mirror of
https://github.com/vale981/tdesktop
synced 2025-03-05 09:41:41 -05:00
Merge branch 'dev'
This commit is contained in:
commit
9378c05ef1
144 changed files with 18763 additions and 11915 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -10,6 +10,7 @@
|
|||
*.suo
|
||||
*.sdf
|
||||
*.opensdf
|
||||
*.opendb
|
||||
/Telegram/*.aps
|
||||
/Win32/
|
||||
ipch/
|
||||
|
|
|
@ -9,9 +9,9 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
|
|||
## Supported systems
|
||||
|
||||
* Windows XP - Windows 10 (**not** RT)
|
||||
* Mac OS X 10.8 - Mac OS X 10.10
|
||||
* Mac OS X 10.8 - Mac OS X 10.11
|
||||
* Mac OS X 10.6 - Mac OS X 10.7 (separate build)
|
||||
* Ubuntu 12.04 - Ubuntu 14.04
|
||||
* Ubuntu 12.04 - Ubuntu 15.04
|
||||
* Fedora 22
|
||||
|
||||
## Third-party libraries
|
||||
|
@ -82,7 +82,7 @@ The source code is published under GPLv3 with OpenSSL exception, the license is
|
|||
|
||||
* ### MetaLang
|
||||
|
||||
Creates from languagepack file `Resources/lang.txt` language constants code and language file parse code:
|
||||
Creates from languagepack file `Resources/lang.strings` language constants code and language file parse code:
|
||||
* GeneratedFiles/lang.h
|
||||
* GeneratedFiles/lang.cpp
|
||||
|
||||
|
|
|
@ -57,7 +57,11 @@ elif [ "$BuildTarget" == "mac" ]; then
|
|||
echo "Deploying version $AppVersionStrFull for Windows.."
|
||||
else
|
||||
DeployMac="1"
|
||||
DeployMac32="1"
|
||||
if [ "$BetaVersion" != "0" ]; then
|
||||
DeployMac32="0"
|
||||
else
|
||||
DeployMac32="1"
|
||||
fi
|
||||
DeployWin="1"
|
||||
echo "Deploying three versions of $AppVersionStrFull: for Windows, OS X 10.6 and 10.7 and OS X 10.8+.."
|
||||
fi
|
||||
|
@ -188,7 +192,6 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$Build
|
|||
fi
|
||||
|
||||
if [ "$DeployMac" == "1" ]; then
|
||||
cp -v "$DeployPath/$UpdateFile" "$DropboxDeployPath/"
|
||||
cp -v "$DeployPath/$SetupFile" "$DropboxDeployPath/$DropboxSetupFile"
|
||||
if [ -d "$DropboxDeployPath/Telegram.app.dSYM" ]; then
|
||||
rm -rf "$DropboxDeployPath/Telegram.app.dSYM"
|
||||
|
@ -196,7 +199,6 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$Build
|
|||
cp -rv "$DeployPath/Telegram.app.dSYM" "$DropboxDeployPath/"
|
||||
fi
|
||||
if [ "$DeployMac32" == "1" ]; then
|
||||
mv -v "$Mac32DeployPath/$Mac32UpdateFile" "$DropboxDeployPath/"
|
||||
mv -v "$Mac32DeployPath/$Mac32SetupFile" "$DropboxDeployPath/$DropboxMac32SetupFile"
|
||||
if [ -d "$DropboxDeployPath/Telegram32.app.dSYM" ]; then
|
||||
rm -rf "$DropboxDeployPath/Telegram32.app.dSYM"
|
||||
|
@ -207,7 +209,6 @@ if [ "$BuildTarget" == "linux" ] || [ "$BuildTarget" == "linux32" ] || [ "$Build
|
|||
mv -v "$WinDeployPath/Telegram.pdb" "$DropboxDeployPath/"
|
||||
mv -v "$WinDeployPath/Updater.exe" "$DropboxDeployPath/"
|
||||
mv -v "$WinDeployPath/Updater.pdb" "$DropboxDeployPath/"
|
||||
mv -v "$WinDeployPath/$WinUpdateFile" "$DropboxDeployPath/"
|
||||
if [ "$BetaVersion" == "0" ]; then
|
||||
mv -v "$WinDeployPath/$WinSetupFile" "$DropboxDeployPath/"
|
||||
fi
|
||||
|
|
|
@ -86,8 +86,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cancel" = "Cancel";
|
||||
"lng_continue" = "Continue";
|
||||
"lng_close" = "Close";
|
||||
"lng_connecting" = "Connecting..";
|
||||
"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}..";
|
||||
"lng_connecting" = "Connecting...";
|
||||
"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}...";
|
||||
"lng_reconnecting_try_now" = "Try now";
|
||||
|
||||
"lng_status_service_notifications" = "service notifications";
|
||||
|
@ -108,7 +108,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_status_lastseen_date" = "last seen {date}";
|
||||
"lng_status_lastseen_date_time" = "last seen {date} at {time}";
|
||||
"lng_status_online" = "online";
|
||||
"lng_status_connecting" = "connecting..";
|
||||
"lng_status_connecting" = "connecting...";
|
||||
|
||||
"lng_chat_status_unaccessible" = "group is unaccessible";
|
||||
"lng_chat_status_members" = "{count:no members|# member|# members}";
|
||||
|
@ -127,7 +127,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_edit_deleted" = "This message was deleted";
|
||||
"lng_edit_too_long" = "Your message text is too long";
|
||||
"lng_edit_message" = "Edit message";
|
||||
"lng_edit_message_text" = "New message text..";
|
||||
"lng_edit_message_text" = "New message text...";
|
||||
"lng_deleted" = "Unknown";
|
||||
"lng_deleted_message" = "Deleted message";
|
||||
"lng_pinned_message" = "Pinned message";
|
||||
|
@ -162,7 +162,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_code_telegram" = "Please enter the code you've just\nreceived in your previous [b]Telegram[/b] app.";
|
||||
"lng_code_no_telegram" = "Send code via SMS";
|
||||
"lng_code_call" = "Telegram will dial your number in {minutes}:{seconds}";
|
||||
"lng_code_calling" = "Requesting a call from Telegram..";
|
||||
"lng_code_calling" = "Requesting a call from Telegram...";
|
||||
"lng_code_called" = "Telegram dialed your number";
|
||||
|
||||
"lng_bad_phone" = "Invalid phone number. Please try again.";
|
||||
|
@ -201,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_dlg_new_channel_name" = "Channel name";
|
||||
"lng_no_contacts" = "You have no contacts";
|
||||
"lng_no_chats" = "Your chats will be here";
|
||||
"lng_contacts_loading" = "Loading..";
|
||||
"lng_contacts_loading" = "Loading...";
|
||||
"lng_contacts_not_found" = "No contacts found";
|
||||
"lng_dlg_search_chat" = "Search in this chat";
|
||||
"lng_dlg_search_channel" = "Search in this channel";
|
||||
|
@ -210,7 +210,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_save" = "Save";
|
||||
"lng_settings_upload" = "Set Profile Photo";
|
||||
"lng_settings_crop_profile" = "Select a square area for your profile photo";
|
||||
"lng_settings_uploading_photo" = "Uploading photo..";
|
||||
"lng_settings_uploading_photo" = "Uploading photo...";
|
||||
|
||||
"lng_username_title" = "Username";
|
||||
"lng_username_about" = "You can choose a username on Telegram.\nIf you do, other people will be able to find\nyou by this username and contact you\nwithout knowing your phone number.\n\nYou can use a-z, 0-9 and underscores.\nMinimum length is 5 characters.";
|
||||
|
@ -247,9 +247,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_auto_update" = "Update automatically";
|
||||
"lng_settings_current_version" = "Version {version}";
|
||||
"lng_settings_check_now" = "Check for updates";
|
||||
"lng_settings_update_checking" = "Checking for updates..";
|
||||
"lng_settings_update_checking" = "Checking for updates...";
|
||||
"lng_settings_latest_installed" = "Latest version is installed";
|
||||
"lng_settings_downloading" = "Downloading update {ready} / {total} MB..";
|
||||
"lng_settings_downloading" = "Downloading update {ready} / {total} MB...";
|
||||
"lng_settings_update_ready" = "New version is ready";
|
||||
"lng_settings_update_now" = "Restart Now";
|
||||
"lng_settings_update_fail" = "Update check failed :(";
|
||||
|
@ -291,7 +291,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_download_path_failed" = "File download could not be started. It could happen because of a bad download location.\n\nYou can change download path in Settings.";
|
||||
"lng_download_path_settings" = "Settings";
|
||||
"lng_download_finish_failed" = "File download could not be finished.\n\nWould you like to try again?";
|
||||
"lng_download_path_clearing" = "Clearing..";
|
||||
"lng_download_path_clearing" = "Clearing...";
|
||||
"lng_download_path_cleared" = "Cleared!";
|
||||
"lng_download_path_clear_failed" = "Clear failed :(";
|
||||
|
||||
|
@ -300,7 +300,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_images_cached" = "{count:_not_used_|# image|# images}, {size}";
|
||||
"lng_settings_audios_cached" = "{count:_not_used_|# voice message|# voice messages}, {size}";
|
||||
"lng_local_storage_clear" = "Clear all";
|
||||
"lng_local_storage_clearing" = "Clearing..";
|
||||
"lng_local_storage_clearing" = "Clearing...";
|
||||
"lng_local_storage_cleared" = "Cleared!";
|
||||
"lng_local_storage_clear_failed" = "Clear failed :(";
|
||||
|
||||
|
@ -331,7 +331,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_passcode_logout" = "Log out";
|
||||
"lng_passcode_need_unblock" = "You need to unlock me first.";
|
||||
|
||||
"lng_cloud_password_waiting" = "Confirmation link sent to {email}..";
|
||||
"lng_cloud_password_waiting" = "Confirmation link sent to {email}...";
|
||||
"lng_cloud_password_change" = "Change cloud password";
|
||||
"lng_cloud_password_create" = "Cloud password";
|
||||
"lng_cloud_password_remove" = "Remove cloud password";
|
||||
|
@ -358,9 +358,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cloud_password_is_same" = "Password was not changed";
|
||||
|
||||
"lng_connection_type" = "Connection type:";
|
||||
"lng_connection_auto_connecting" = "Default (connecting..)";
|
||||
"lng_connection_auto_connecting" = "Default (connecting...)";
|
||||
"lng_connection_auto" = "Default ({transport} used)";
|
||||
"lng_connection_proxy_connecting" = "Connecting through proxy..";
|
||||
"lng_connection_proxy_connecting" = "Connecting through proxy...";
|
||||
"lng_connection_proxy" = "{transport} with proxy";
|
||||
"lng_connection_header" = "Connection type";
|
||||
"lng_connection_auto_rb" = "Auto (TCP if available or HTTP)";
|
||||
|
@ -396,7 +396,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_sessions_other_desc" = "You can log in to Telegram from other mobile, tablet and desktop devices, using the same phone number. All your data will be instantly synchronized.";
|
||||
"lng_sessions_terminate_all" = "Terminate all other sessions";
|
||||
|
||||
"lng_preview_loading" = "Getting Link Info..";
|
||||
"lng_preview_loading" = "Getting Link Info...";
|
||||
|
||||
"lng_profile_chat_unaccessible" = "Group is unaccessible";
|
||||
"lng_topbar_info" = "Info";
|
||||
|
@ -439,7 +439,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_sure_kick" = "Remove {user} from the group?";
|
||||
"lng_profile_sure_kick_channel" = "Remove {user} from the channel?";
|
||||
"lng_profile_sure_kick_admin" = "Remove {user} from administrators?";
|
||||
"lng_profile_loading" = "Loading..";
|
||||
"lng_profile_loading" = "Loading...";
|
||||
"lng_profile_shared_media" = "Shared media";
|
||||
"lng_profile_no_media" = "No media in this conversation.";
|
||||
"lng_profile_photos" = "{count:_not_used_|# photo|# photos} »";
|
||||
|
@ -455,6 +455,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_shared_links" = "{count:_not_used_|# shared link|# shared links} »";
|
||||
"lng_profile_shared_links_header" = "Shared links overview";
|
||||
"lng_profile_copy_phone" = "Copy phone number";
|
||||
"lng_profile_copy_fullname" = "Copy name";
|
||||
|
||||
"lng_channel_add_admins" = "New administrator";
|
||||
"lng_channel_add_members" = "Add members";
|
||||
|
@ -659,12 +660,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_remove" = "Delete";
|
||||
"lng_stickers_return" = "Undo";
|
||||
"lng_stickers_restore" = "Restore";
|
||||
"lng_stickers_count" = "{count:Loading..|# sticker|# stickers}";
|
||||
"lng_stickers_count" = "{count:Loading...|# sticker|# stickers}";
|
||||
|
||||
"lng_in_dlg_photo" = "Photo";
|
||||
"lng_in_dlg_video" = "Video";
|
||||
"lng_in_dlg_video" = "Video file";
|
||||
"lng_in_dlg_audio_file" = "Audio file";
|
||||
"lng_in_dlg_contact" = "Contact";
|
||||
"lng_in_dlg_audio" = "Audio";
|
||||
"lng_in_dlg_audio" = "Voice message";
|
||||
"lng_in_dlg_file" = "File";
|
||||
"lng_in_dlg_sticker" = "Sticker";
|
||||
"lng_in_dlg_sticker_emoji" = "{emoji} (sticker)";
|
||||
|
@ -678,18 +680,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_report_spam_sure_group" = "Are you sure you want to report spam in this group?";
|
||||
"lng_report_spam_sure_channel" = "Are you sure you want to report spam in this channel?";
|
||||
"lng_report_spam_ok" = "Report";
|
||||
"lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment. {more_info}";
|
||||
"lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment. {more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Sorry, you can only add mutual contacts\nto channels at the moment. {more_info}";
|
||||
"lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment.\n{more_info}";
|
||||
"lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment.\n{more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Sorry, you can only add mutual contacts\nto channels at the moment.\n{more_info}";
|
||||
"lng_cant_more_info" = "More info »";
|
||||
"lng_cant_invite_banned" = "Sorry, only admin can add this user.";
|
||||
"lng_cant_invite_privacy" = "Sorry, you cannot add this user to groups because of the privacy settings.";
|
||||
"lng_cant_invite_privacy_channel" = "Sorry, you cannot add this user to channels because of the privacy settings.";
|
||||
"lng_cant_do_this" = "Sorry, this action is unavailable.";
|
||||
|
||||
"lng_send_button" = "Send";
|
||||
"lng_message_ph" = "Write a message..";
|
||||
"lng_comment_ph" = "Write a comment..";
|
||||
"lng_broadcast_ph" = "Broadcast a message..";
|
||||
"lng_broadcast_silent_ph" = "Silent broadcast..";
|
||||
"lng_message_ph" = "Write a message...";
|
||||
"lng_comment_ph" = "Write a comment...";
|
||||
"lng_broadcast_ph" = "Broadcast a message...";
|
||||
"lng_broadcast_silent_ph" = "Silent broadcast...";
|
||||
"lng_record_cancel" = "Release outside this field to cancel";
|
||||
"lng_will_be_notified" = "Members will be notified when you post";
|
||||
"lng_wont_be_notified" = "Members will not be notified when you post";
|
||||
|
@ -717,28 +721,29 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_user_typing" = "{user} is typing";
|
||||
"lng_users_typing" = "{user} and {second_user} are typing";
|
||||
"lng_many_typing" = "{count:_not_used_|# is|# are} typing";
|
||||
"lng_send_action_record_video" = "recording video";
|
||||
"lng_user_action_record_video" = "{user} is recording video";
|
||||
"lng_send_action_upload_video" = "sending video";
|
||||
"lng_user_action_upload_video" = "{user} is sending video";
|
||||
"lng_send_action_record_audio" = "recording audio";
|
||||
"lng_user_action_record_audio" = "{user} is recording audio";
|
||||
"lng_send_action_upload_audio" = "sending audio";
|
||||
"lng_user_action_upload_audio" = "{user} is sending audio";
|
||||
"lng_send_action_upload_photo" = "sending photo";
|
||||
"lng_user_action_upload_photo" = "{user} is sending photo";
|
||||
"lng_send_action_upload_file" = "sending file";
|
||||
"lng_user_action_upload_file" = "{user} is sending file";
|
||||
"lng_send_action_geo_location" = "picking location";
|
||||
"lng_user_action_geo_location" = "{user} is picking location";
|
||||
"lng_send_action_choose_contact" = "choosing contact";
|
||||
"lng_user_action_choose_contact" = "{user} is choosing contact";
|
||||
"lng_send_action_record_video" = "recording a video";
|
||||
"lng_user_action_record_video" = "{user} is recording a video";
|
||||
"lng_send_action_upload_video" = "sending a video";
|
||||
"lng_user_action_upload_video" = "{user} is sending a video";
|
||||
"lng_send_action_record_audio" = "recording a voice message";
|
||||
"lng_user_action_record_audio" = "{user} is recording a voice message";
|
||||
"lng_send_action_upload_audio" = "sending a voice message";
|
||||
"lng_user_action_upload_audio" = "{user} is sending a voice message";
|
||||
"lng_send_action_upload_photo" = "sending a photo";
|
||||
"lng_user_action_upload_photo" = "{user} is sending a photo";
|
||||
"lng_send_action_upload_file" = "sending a file";
|
||||
"lng_user_action_upload_file" = "{user} is sending a file";
|
||||
"lng_send_action_geo_location" = "picking a location";
|
||||
"lng_user_action_geo_location" = "{user} is picking a location";
|
||||
"lng_send_action_choose_contact" = "choosing a contact";
|
||||
"lng_user_action_choose_contact" = "{user} is choosing a contact";
|
||||
"lng_unread_bar" = "{count:_not_used_|# unread message|# unread messages}";
|
||||
|
||||
"lng_maps_point" = "Location";
|
||||
"lng_save_photo" = "Save image";
|
||||
"lng_save_video" = "Save video";
|
||||
"lng_save_audio" = "Save audio";
|
||||
"lng_save_video" = "Save video file";
|
||||
"lng_save_audio_file" = "Save audio file";
|
||||
"lng_save_audio" = "Save voice message";
|
||||
"lng_save_file" = "Save file";
|
||||
"lng_save_downloaded" = "{ready} / {total} {mb}";
|
||||
"lng_duration_and_size" = "{duration}, {size}";
|
||||
|
@ -755,7 +760,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_copy_email" = "Copy email address";
|
||||
"lng_context_copy_hashtag" = "Copy hashtag";
|
||||
"lng_context_copy_mention" = "Copy username";
|
||||
"lng_context_save_image" = "Save Image As..";
|
||||
"lng_context_save_image" = "Save Image As...";
|
||||
"lng_context_forward_image" = "Forward Image";
|
||||
"lng_context_delete_image" = "Delete Image";
|
||||
"lng_context_copy_image" = "Copy Image";
|
||||
|
@ -763,11 +768,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_cancel_download" = "Cancel Download";
|
||||
"lng_context_show_in_folder" = "Show in Folder";
|
||||
"lng_context_show_in_finder" = "Show in Finder";
|
||||
"lng_context_save_video" = "Save Video As..";
|
||||
"lng_context_save_audio" = "Save Audio As..";
|
||||
"lng_context_save_video" = "Save Video File As...";
|
||||
"lng_context_save_audio_file" = "Save Audio File As...";
|
||||
"lng_context_save_audio" = "Save Voice Message As...";
|
||||
"lng_context_pack_info" = "Pack Info";
|
||||
"lng_context_pack_add" = "Add Stickers";
|
||||
"lng_context_save_file" = "Save File As..";
|
||||
"lng_context_save_file" = "Save File As...";
|
||||
"lng_context_forward_file" = "Forward File";
|
||||
"lng_context_delete_file" = "Delete File";
|
||||
"lng_context_close_file" = "Close File";
|
||||
|
@ -794,7 +800,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_send_image_too_large" = "Could not send a file, because it is larger than 1.5 GB :(";
|
||||
"lng_send_folder" = "Could not send «{name}» because it is a directory :(";
|
||||
|
||||
"lng_forward_choose" = "Choose recipient..";
|
||||
"lng_forward_choose" = "Choose recipient...";
|
||||
"lng_forward_cant" = "Sorry, no way to forward here :(";
|
||||
"lng_forward_confirm" = "Forward to {recipient}?";
|
||||
"lng_forward_share_contact" = "Share contact to {recipient}?";
|
||||
|
@ -857,7 +863,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_search_global_results" = "Global search results";
|
||||
|
||||
"lng_media_save_progress" = "{ready} of {total} {mb}";
|
||||
"lng_mediaview_save_as" = "Save As..";
|
||||
"lng_mediaview_save_as" = "Save As...";
|
||||
"lng_mediaview_copy" = "Copy";
|
||||
"lng_mediaview_forward" = "Forward";
|
||||
"lng_mediaview_delete" = "Delete";
|
||||
|
|
|
@ -975,6 +975,21 @@ topBarBackAlpha: 0.8;
|
|||
topBarBackImg: sprite(65px, 112px, 9px, 16px);
|
||||
topBarBackColor: #005faf;
|
||||
topBarBackFont: font(16px);
|
||||
topBarSearch: iconedButton(btnDefIconed) {
|
||||
bgColor: transparent;
|
||||
overBgColor: transparent;
|
||||
|
||||
icon: sprite(84px, 374px, 18px, 18px);
|
||||
iconPos: point(13px, 18px);
|
||||
downIcon: sprite(84px, 374px, 18px, 18px);
|
||||
downIconPos: point(13px, 18px);
|
||||
|
||||
opacity: 0.22;
|
||||
overOpacity: 0.36;
|
||||
|
||||
width: 44px;
|
||||
height: topBarHeight;
|
||||
}
|
||||
topBarMinPadding: 5px;
|
||||
topBarButton: flatButton(btnDefFlat) {
|
||||
color: btnYesColor;
|
||||
|
@ -1034,10 +1049,11 @@ msgServiceNameFont: semiboldFont;
|
|||
msgServicePhotoWidth: 100px;
|
||||
msgDateFont: font(13px);
|
||||
msgMinWidth: 190px;
|
||||
msgPhotoSize: 30px;
|
||||
msgPhotoSize: 33px;
|
||||
msgPhotoSkip: 40px;
|
||||
msgPadding: margins(13px, 7px, 13px, 8px);
|
||||
msgMargin: margins(13px, 4px, 53px, 4px);
|
||||
msgMargin: margins(13px, 6px, 53px, 2px);
|
||||
msgMarginTopAttached: 2px;
|
||||
msgLnkPadding: 2px; // for media open / save links
|
||||
msgBorder: #f0f0f0;
|
||||
msgInBg: #fff;
|
||||
|
@ -1073,7 +1089,7 @@ msgServiceBg: #89a0b47f;
|
|||
msgServiceSelectBg: #bbc8d4a2;
|
||||
msgServiceColor: #FFF;
|
||||
msgServicePadding: margins(12px, 3px, 12px, 4px);
|
||||
msgServiceMargin: margins(10px, 7px, 80px, 7px);
|
||||
msgServiceMargin: margins(10px, 9px, 80px, 5px);
|
||||
|
||||
msgColor: #000;
|
||||
msgDateColor: #000;
|
||||
|
@ -1132,7 +1148,7 @@ collapseButton: flatButton(btnDefFlat) {
|
|||
textTop: 3px;
|
||||
overTextTop: 3px;
|
||||
downTextTop: 3px;
|
||||
height: 24px;
|
||||
height: 25px;
|
||||
}
|
||||
collapseHideDuration: 200;
|
||||
collapseShowDuration: 200;
|
||||
|
|
|
@ -189,7 +189,7 @@ void readKeyValue(const char *&from, const char *end) {
|
|||
|
||||
if (*from == ':') {
|
||||
start = ++from;
|
||||
|
||||
|
||||
QVector<QString> &counted(keysCounted[varName][tagName]);
|
||||
QByteArray subvarValue;
|
||||
bool foundtag = false;
|
||||
|
@ -391,7 +391,7 @@ bool genLang(const QString &lang_in, const QString &lang_out) {
|
|||
th.setCodec("ISO 8859-1");
|
||||
th << "\
|
||||
/*\n\
|
||||
Created from \'/Resources/lang.txt\' by \'/MetaLang\' project\n\
|
||||
Created from \'/Resources/lang.strings\' by \'/MetaLang\' project\n\
|
||||
\n\
|
||||
WARNING! All changes made in this file will be lost!\n\
|
||||
\n\
|
||||
|
@ -475,7 +475,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
|
|||
|
||||
tcpp << "\
|
||||
/*\n\
|
||||
Created from \'/Resources/lang.txt\' by \'/MetaLang\' project\n\
|
||||
Created from \'/Resources/lang.strings\' by \'/MetaLang\' project\n\
|
||||
\n\
|
||||
WARNING! All changes made in this file will be lost!\n\
|
||||
\n\
|
||||
|
@ -606,13 +606,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
|
|||
++depth;
|
||||
current += ich;
|
||||
|
||||
if (tag == current) {
|
||||
bool exact = (tag == current);
|
||||
if (exact) {
|
||||
tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " == e) {\n";
|
||||
tcpp << tab.repeated(depth + 1) << "\treturn lt_" << tag << ";\n";
|
||||
tcpp << tab.repeated(depth + 1) << "}\n";
|
||||
}
|
||||
|
||||
tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n";
|
||||
QByteArray nexttag = j.key();
|
||||
if (exact && depth > 0 && nexttag.mid(0, depth) != current) {
|
||||
current.chop(1);
|
||||
--depth;
|
||||
tcpp << tab.repeated(depth + 1) << "break;\n";
|
||||
break;
|
||||
} else {
|
||||
tcpp << tab.repeated(depth + 1) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n";
|
||||
}
|
||||
} while (true);
|
||||
++j;
|
||||
}
|
||||
|
@ -637,7 +646,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
|
|||
tcpp << "\tswitch (*(ch + " << depth << ")) {\n";
|
||||
for (LangKeys::const_iterator i = keys.cbegin(), j = i + 1, e = keys.cend(); i != e; ++i) {
|
||||
QByteArray key = i.key();
|
||||
while (key.mid(0, depth) != current) {
|
||||
while (depth > 0 && key.mid(0, depth) != current) {
|
||||
tcpp << tab.repeated(depth - 3) << "}\n";
|
||||
current.chop(1);
|
||||
--depth;
|
||||
|
@ -645,7 +654,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
|
|||
}
|
||||
do {
|
||||
if (key == current) break;
|
||||
|
||||
|
||||
char ich = i.key().at(current.size());
|
||||
tcpp << tab.repeated(current.size() - 3) << "case '" << ich << "':\n";
|
||||
if (j == e || ich != ((j.key().size() > depth) ? j.key().at(depth) : 0)) {
|
||||
|
@ -661,13 +670,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
|
|||
++depth;
|
||||
current += ich;
|
||||
|
||||
if (key == current) {
|
||||
bool exact = (key == current);
|
||||
if (exact) {
|
||||
tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " == e) {\n";
|
||||
tcpp << tab.repeated(depth - 3) << "\treturn " << key << (keysTags[key].isEmpty() ? "" : "__tagged") << ";\n";
|
||||
tcpp << tab.repeated(depth - 3) << "}\n";
|
||||
}
|
||||
|
||||
tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n";
|
||||
QByteArray nextkey = j.key();
|
||||
if (exact && depth > 0 && nextkey.mid(0, depth) != current) {
|
||||
current.chop(1);
|
||||
--depth;
|
||||
tcpp << tab.repeated(depth - 3) << "break;\n";
|
||||
break;
|
||||
} else {
|
||||
tcpp << tab.repeated(depth - 3) << "if (ch + " << depth << " < e) switch (*(ch + " << depth << ")) {\n";
|
||||
}
|
||||
} while (true);
|
||||
++j;
|
||||
}
|
||||
|
@ -707,16 +725,25 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
|
|||
tcpp << "\tif (index >= lngtags_max_counted_values) return lngkeys_cnt;\n\n";
|
||||
if (!tags.isEmpty()) {
|
||||
tcpp << "\tswitch (key) {\n";
|
||||
for (int i = 0, l = keysOrder.size(); i < l; ++i) {
|
||||
QVector<QByteArray> &tagsList(keysTags[keysOrder[i]]);
|
||||
for (auto key : keysOrder) {
|
||||
QVector<QByteArray> &tagsList(keysTags[key]);
|
||||
if (tagsList.isEmpty()) continue;
|
||||
|
||||
QMap<QByteArray, QVector<QString> > &countedTags(keysCounted[keysOrder[i]]);
|
||||
tcpp << "\tcase " << keysOrder[i] << "__tagged: {\n";
|
||||
QMap<QByteArray, QVector<QString> > &countedTags(keysCounted[key]);
|
||||
bool hasCounted = false;
|
||||
for (auto tag : tagsList) {
|
||||
if (!countedTags[tag].isEmpty()) {
|
||||
hasCounted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasCounted) continue;
|
||||
|
||||
tcpp << "\tcase " << key << "__tagged: {\n";
|
||||
tcpp << "\t\tswitch (tag) {\n";
|
||||
for (int j = 0, s = tagsList.size(); j < s; ++j) {
|
||||
if (!countedTags[tagsList[j]].isEmpty()) {
|
||||
tcpp << "\t\tcase lt_" << tagsList[j] << ": return LangKey(" << keysOrder[i] << "__" << tagsList[j] << "0 + index);\n";
|
||||
for (auto tag : tagsList) {
|
||||
if (!countedTags[tag].isEmpty()) {
|
||||
tcpp << "\t\tcase lt_" << tag << ": return LangKey(" << key << "__" << tag << "0 + index);\n";
|
||||
}
|
||||
}
|
||||
tcpp << "\t\t}\n";
|
||||
|
@ -724,7 +751,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org\n\
|
|||
}
|
||||
tcpp << "\t}\n\n";
|
||||
}
|
||||
tcpp << "\treturn lngkeys_cnt;";
|
||||
tcpp << "\treturn lngkeys_cnt;\n";
|
||||
tcpp << "}\n\n";
|
||||
|
||||
tcpp << "bool LangLoader::feedKeyValue(LangKey key, const QString &value) {\n";
|
||||
|
|
|
@ -230,7 +230,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt
|
|||
}
|
||||
if (f.has_migrated_from_chat_id()) {
|
||||
if (!channel->mgInfo) {
|
||||
channel->flags |= MTPDchannel::flag_megagroup;
|
||||
channel->flags |= MTPDchannel::Flag::f_megagroup;
|
||||
channel->flagsUpdated();
|
||||
}
|
||||
ChatData *cfrom = App::chat(peerFromChat(f.vmigrated_from_chat_id));
|
||||
|
@ -685,8 +685,8 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
|
|||
if (d.vset.type() != mtpc_stickerSet) return;
|
||||
const MTPDstickerSet &s(d.vset.c_stickerSet());
|
||||
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
StickerSets::iterator it = sets.find(setId);
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
auto it = sets.find(setId);
|
||||
if (it == sets.cend()) return;
|
||||
|
||||
it->access = s.vaccess_hash.v;
|
||||
|
@ -696,7 +696,7 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
|
|||
it->flags = s.vflags.v;
|
||||
|
||||
const QVector<MTPDocument> &d_docs(d.vdocuments.c_vector().v);
|
||||
StickerSets::iterator custom = sets.find(CustomStickerSetId);
|
||||
auto custom = sets.find(Stickers::CustomSetId);
|
||||
|
||||
StickerPack pack;
|
||||
pack.reserve(d_docs.size());
|
||||
|
@ -729,8 +729,8 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
|
|||
}
|
||||
|
||||
if (pack.isEmpty()) {
|
||||
int32 removeIndex = cStickerSetsOrder().indexOf(setId);
|
||||
if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex);
|
||||
int removeIndex = Global::StickerSetsOrder().indexOf(setId);
|
||||
if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex);
|
||||
sets.erase(it);
|
||||
} else {
|
||||
it->stickers = pack;
|
||||
|
@ -903,10 +903,8 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
|
|||
}
|
||||
|
||||
for (QMap<uint64, int32>::const_iterator i = msgsIds.cbegin(), e = msgsIds.cend(); i != e; ++i) {
|
||||
HistoryItem *item = App::histories().addNewMessage(v->at(i.value()), NewMessageExisting);
|
||||
if (item) {
|
||||
item->initDimensions();
|
||||
Notify::historyItemResized(item);
|
||||
if (HistoryItem *item = App::histories().addNewMessage(v->at(i.value()), NewMessageExisting)) {
|
||||
item->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -918,8 +916,7 @@ void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &msgs
|
|||
WebPageItems::const_iterator j = items.constFind(i.key());
|
||||
if (j != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) {
|
||||
k.key()->initDimensions();
|
||||
Notify::historyItemResized(k.key());
|
||||
k.key()->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace {
|
|||
|
||||
typedef QMap<MsgId, ReplyMarkup> ReplyMarkups;
|
||||
ReplyMarkups replyMarkups;
|
||||
ReplyMarkup zeroMarkup(MTPDreplyKeyboardMarkup_flag_ZERO);
|
||||
ReplyMarkup zeroMarkup(qFlags(MTPDreplyKeyboardMarkup_ClientFlag::f_zero));
|
||||
typedef QMap<ChannelId, ReplyMarkups> ChannelReplyMarkups;
|
||||
ChannelReplyMarkups channelReplyMarkups;
|
||||
|
||||
|
@ -67,7 +67,8 @@ namespace {
|
|||
SharedContactItems sharedContactItems;
|
||||
GifItems gifItems;
|
||||
|
||||
typedef QMap<HistoryItem*, OrderedSet<HistoryItem*> > DependentItems;
|
||||
typedef OrderedSet<HistoryItem*> DependentItemsSet;
|
||||
typedef QMap<HistoryItem*, DependentItemsSet> DependentItems;
|
||||
DependentItems dependentItems;
|
||||
|
||||
Histories histories;
|
||||
|
@ -326,6 +327,25 @@ namespace App {
|
|||
return lng_status_lastseen_date(lt_date, dOnline.date().toString(qsl("dd.MM.yy")));
|
||||
}
|
||||
|
||||
namespace {
|
||||
// we should get a full restriction in "{fulltype}: {reason}" format and we
|
||||
// need to find a "-all" tag in {fulltype}, otherwise ignore this restriction
|
||||
QString extractRestrictionReason(const QString &fullRestriction) {
|
||||
int fullTypeEnd = fullRestriction.indexOf(':');
|
||||
if (fullTypeEnd <= 0) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
// {fulltype} is in "{type}-{tag}-{tag}-{tag}" format
|
||||
// if we find "all" tag we return the restriction string
|
||||
QStringList typeTags = fullRestriction.mid(0, fullTypeEnd).split('-').mid(1);
|
||||
if (typeTags.contains(qsl("all"))) {
|
||||
return fullRestriction.midRef(fullTypeEnd + 1).trimmed().toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
bool onlineColorUse(UserData *user, int32 now) {
|
||||
if (isServiceUser(user->id) || user->botInfo) {
|
||||
return false;
|
||||
|
@ -388,6 +408,11 @@ namespace App {
|
|||
data->input = MTP_inputPeerUser(d.vid, d.vaccess_hash);
|
||||
data->inputUser = MTP_inputUser(d.vid, d.vaccess_hash);
|
||||
}
|
||||
if (d.is_restricted()) {
|
||||
data->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason)));
|
||||
} else {
|
||||
data->setRestrictionReason(QString());
|
||||
}
|
||||
}
|
||||
if (d.is_deleted()) {
|
||||
data->setPhone(QString());
|
||||
|
@ -396,9 +421,13 @@ namespace App {
|
|||
data->access = UserNoAccess;
|
||||
status = &emptyStatus;
|
||||
} else {
|
||||
// apply first_name and last_name from minimal user only if we don't have
|
||||
// local values for first name and last name already, otherwise skip
|
||||
bool noLocalName = data->firstName.isEmpty() && data->lastName.isEmpty();
|
||||
QString fname = (!minimal || noLocalName) ? (d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString()) : data->firstName;
|
||||
QString lname = (!minimal || noLocalName) ? (d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString()) : data->lastName;
|
||||
|
||||
QString phone = minimal ? data->phone : (d.has_phone() ? qs(d.vphone) : QString());
|
||||
QString fname = d.has_first_name() ? textOneLine(qs(d.vfirst_name)) : QString();
|
||||
QString lname = d.has_last_name() ? textOneLine(qs(d.vlast_name)) : QString();
|
||||
QString uname = minimal ? data->username : (d.has_username() ? textOneLine(qs(d.vusername)) : QString());
|
||||
|
||||
bool phoneChanged = (data->phone != phone);
|
||||
|
@ -450,7 +479,13 @@ namespace App {
|
|||
|
||||
if (!data) continue;
|
||||
|
||||
data->loaded = true;
|
||||
if (minimal) {
|
||||
if (data->loadedStatus == PeerData::NotLoaded) {
|
||||
data->loadedStatus = PeerData::MinimalLoaded;
|
||||
}
|
||||
} else if (data->loadedStatus != PeerData::FullLoaded) {
|
||||
data->loadedStatus = PeerData::FullLoaded;
|
||||
}
|
||||
if (status && !minimal) switch (status->type()) {
|
||||
case mtpc_userStatusEmpty: data->onlineTill = 0; break;
|
||||
case mtpc_userStatusRecently:
|
||||
|
@ -507,7 +542,7 @@ namespace App {
|
|||
const MTPDinputChannel &c(d.vmigrated_to.c_inputChannel());
|
||||
ChannelData *channel = App::channel(peerFromChannel(c.vchannel_id));
|
||||
if (!channel->mgInfo) {
|
||||
channel->flags |= MTPDchannel::flag_megagroup;
|
||||
channel->flags |= MTPDchannel::Flag::f_megagroup;
|
||||
channel->flagsUpdated();
|
||||
}
|
||||
if (!channel->access) {
|
||||
|
@ -538,7 +573,7 @@ namespace App {
|
|||
}
|
||||
}
|
||||
|
||||
if (!(cdata->flags & MTPDchat::flag_admins_enabled) && (d.vflags.v & MTPDchat::flag_admins_enabled)) {
|
||||
if (!(cdata->flags & MTPDchat::Flag::f_admins_enabled) && (d.vflags.v & MTPDchat::Flag::f_admins_enabled)) {
|
||||
cdata->invalidateParticipants();
|
||||
}
|
||||
cdata->flags = d.vflags.v;
|
||||
|
@ -583,7 +618,7 @@ namespace App {
|
|||
|
||||
ChannelData *cdata = data->asChannel();
|
||||
if (minimal) {
|
||||
int32 mask = MTPDchannel::flag_broadcast | MTPDchannel::flag_verified | MTPDchannel::flag_megagroup | MTPDchannel::flag_democracy;
|
||||
int32 mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy;
|
||||
cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask);
|
||||
} else {
|
||||
cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash);
|
||||
|
@ -593,6 +628,11 @@ namespace App {
|
|||
if (cdata->version < d.vversion.v) {
|
||||
cdata->version = d.vversion.v;
|
||||
}
|
||||
if (d.is_restricted()) {
|
||||
cdata->setRestrictionReason(extractRestrictionReason(qs(d.vrestriction_reason)));
|
||||
} else {
|
||||
cdata->setRestrictionReason(QString());
|
||||
}
|
||||
}
|
||||
QString uname = d.has_username() ? textOneLine(qs(d.vusername)) : QString();
|
||||
cdata->setName(qs(d.vtitle), uname);
|
||||
|
@ -622,7 +662,13 @@ namespace App {
|
|||
}
|
||||
if (!data) continue;
|
||||
|
||||
data->loaded = true;
|
||||
if (minimal) {
|
||||
if (data->loadedStatus == PeerData::NotLoaded) {
|
||||
data->loadedStatus = PeerData::MinimalLoaded;
|
||||
}
|
||||
} else if (data->loadedStatus != PeerData::FullLoaded) {
|
||||
data->loadedStatus = PeerData::FullLoaded;
|
||||
}
|
||||
if (App::main()) {
|
||||
if (emitPeerUpdated) {
|
||||
App::main()->peerUpdated(data);
|
||||
|
@ -654,7 +700,7 @@ namespace App {
|
|||
int32 pversion = chat->participants.isEmpty() ? 1 : (chat->participants.begin().value() + 1);
|
||||
chat->invitedByMe = ChatData::InvitedByMe();
|
||||
chat->admins = ChatData::Admins();
|
||||
chat->flags &= ~MTPDchat::flag_admin;
|
||||
chat->flags &= ~MTPDchat::Flag::f_admin;
|
||||
for (QVector<MTPChatParticipant>::const_iterator i = v.cbegin(), e = v.cend(); i != e; ++i) {
|
||||
int32 uid = 0, inviter = 0;
|
||||
switch (i->type()) {
|
||||
|
@ -685,7 +731,7 @@ namespace App {
|
|||
if (i->type() == mtpc_chatParticipantAdmin) {
|
||||
chat->admins.insert(user);
|
||||
if (user->isSelf()) {
|
||||
chat->flags |= MTPDchat::flag_admin;
|
||||
chat->flags |= MTPDchat::Flag::f_admin;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -805,7 +851,7 @@ namespace App {
|
|||
chat->invitedByMe.remove(user);
|
||||
chat->admins.remove(user);
|
||||
if (user->isSelf()) {
|
||||
chat->flags &= ~MTPDchat::flag_admin;
|
||||
chat->flags &= ~MTPDchat::Flag::f_admin;
|
||||
}
|
||||
|
||||
History *h = App::historyLoaded(chat->id);
|
||||
|
@ -852,13 +898,12 @@ namespace App {
|
|||
}
|
||||
chat->version = d.vversion.v;
|
||||
if (mtpIsTrue(d.venabled)) {
|
||||
chat->flags |= MTPDchat::flag_admins_enabled;
|
||||
chat->flags |= MTPDchat::Flag::f_admins_enabled;
|
||||
if (!badVersion) {
|
||||
chat->invalidateParticipants();
|
||||
}
|
||||
} else {
|
||||
chat->flags &= ~MTPDchat::flag_admins_enabled;
|
||||
chat->flags &= ~MTPDchat::flag_admin;
|
||||
chat->flags &= ~MTPDchat::Flag::f_admins_enabled;
|
||||
}
|
||||
if (emitPeerUpdated) {
|
||||
App::main()->peerUpdated(chat);
|
||||
|
@ -887,7 +932,7 @@ namespace App {
|
|||
if (user) {
|
||||
if (mtpIsTrue(d.vis_admin)) {
|
||||
if (user->isSelf()) {
|
||||
chat->flags |= MTPDchat::flag_admin;
|
||||
chat->flags |= MTPDchat::Flag::f_admin;
|
||||
}
|
||||
if (chat->noParticipantInfo()) {
|
||||
App::api()->requestFullPeer(chat);
|
||||
|
@ -896,7 +941,7 @@ namespace App {
|
|||
}
|
||||
} else {
|
||||
if (user->isSelf()) {
|
||||
chat->flags &= ~MTPDchat::flag_admin;
|
||||
chat->flags &= ~MTPDchat::Flag::f_admin;
|
||||
}
|
||||
chat->admins.remove(user);
|
||||
}
|
||||
|
@ -921,10 +966,7 @@ namespace App {
|
|||
if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
|
||||
existing->setText(qs(m.vmessage), m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText());
|
||||
existing->updateMedia(m.has_media() ? (&m.vmedia) : 0);
|
||||
existing->setViewsCount(m.has_views() ? m.vviews.v : -1, false);
|
||||
existing->initDimensions();
|
||||
Notify::historyItemResized(existing);
|
||||
|
||||
existing->setViewsCount(m.has_views() ? m.vviews.v : -1);
|
||||
existing->addToOverview(AddToOverviewNew);
|
||||
|
||||
if (!existing->detached()) {
|
||||
|
@ -945,16 +987,13 @@ namespace App {
|
|||
if (HistoryItem *existing = App::histItemById(peerToChannel(peerId), m.vid.v)) {
|
||||
existing->setText(qs(m.vmessage), m.has_entities() ? entitiesFromMTP(m.ventities.c_vector().v) : EntitiesInText());
|
||||
existing->updateMedia(m.has_media() ? (&m.vmedia) : 0, true);
|
||||
existing->setViewsCount(m.has_views() ? m.vviews.v : -1, false);
|
||||
existing->initDimensions();
|
||||
existing->setViewsCount(m.has_views() ? m.vviews.v : -1);
|
||||
if (existing->history()->textCachedFor == existing) {
|
||||
existing->history()->textCachedFor = 0;
|
||||
}
|
||||
if (App::main()) {
|
||||
App::main()->dlgUpdated(existing->history(), existing->id);
|
||||
}
|
||||
App::historyUpdateDependent(existing);
|
||||
Notify::historyItemResized(existing);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -974,7 +1013,7 @@ namespace App {
|
|||
}
|
||||
|
||||
void checkSavedGif(HistoryItem *item) {
|
||||
if (!item->Is<HistoryMessageForwarded>() && (item->out() || item->history()->peer == App::self())) {
|
||||
if (!item->Has<HistoryMessageForwarded>() && (item->out() || item->history()->peer == App::self())) {
|
||||
if (HistoryMedia *media = item->getMedia()) {
|
||||
if (DocumentData *doc = media->getDocument()) {
|
||||
if (doc->isGifv()) {
|
||||
|
@ -1095,7 +1134,6 @@ namespace App {
|
|||
}
|
||||
|
||||
void feedWereDeleted(ChannelId channelId, const QVector<MTPint> &msgsIds) {
|
||||
bool resized = false;
|
||||
MsgsData *data = fetchMsgsData(channelId, false);
|
||||
if (!data) return;
|
||||
|
||||
|
@ -1106,9 +1144,6 @@ namespace App {
|
|||
MsgsData::const_iterator j = data->constFind(i->v);
|
||||
if (j != data->cend()) {
|
||||
History *h = (*j)->history();
|
||||
if (App::main() && (h->peer == App::main()->peer() || (App::main()->peer() && h->peer->migrateTo() == App::main()->peer())) && !(*j)->detached()) {
|
||||
resized = true;
|
||||
}
|
||||
(*j)->destroy();
|
||||
if (!h->lastMsg) historiesToCheck.insert(h, true);
|
||||
} else {
|
||||
|
@ -1120,9 +1155,6 @@ namespace App {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (resized) {
|
||||
Notify::historyItemsResized();
|
||||
}
|
||||
if (main()) {
|
||||
for (QMap<History*, bool>::const_iterator i = historiesToCheck.cbegin(), e = historiesToCheck.cend(); i != e; ++i) {
|
||||
main()->checkPeerHistory(i.key()->peer);
|
||||
|
@ -1375,41 +1407,16 @@ namespace App {
|
|||
return 0;
|
||||
}
|
||||
|
||||
PeerData *peerLoaded(const PeerId &peer) {
|
||||
PeersData::const_iterator i = peersData.constFind(peer);
|
||||
return (i != peersData.cend()) ? i.value() : 0;
|
||||
}
|
||||
|
||||
UserData *userLoaded(const PeerId &id) {
|
||||
PeerData *peer = peerLoaded(id);
|
||||
return (peer && peer->loaded) ? peer->asUser() : 0;
|
||||
}
|
||||
ChatData *chatLoaded(const PeerId &id) {
|
||||
PeerData *peer = peerLoaded(id);
|
||||
return (peer && peer->loaded) ? peer->asChat() : 0;
|
||||
}
|
||||
ChannelData *channelLoaded(const PeerId &id) {
|
||||
PeerData *peer = peerLoaded(id);
|
||||
return (peer && peer->loaded) ? peer->asChannel() : 0;
|
||||
}
|
||||
UserData *userLoaded(int32 user_id) {
|
||||
return userLoaded(peerFromUser(user_id));
|
||||
}
|
||||
ChatData *chatLoaded(int32 chat_id) {
|
||||
return chatLoaded(peerFromChat(chat_id));
|
||||
}
|
||||
ChannelData *channelLoaded(int32 channel_id) {
|
||||
return channelLoaded(peerFromChannel(channel_id));
|
||||
}
|
||||
|
||||
UserData *curUser() {
|
||||
return user(MTP::authedId());
|
||||
}
|
||||
|
||||
PeerData *peer(const PeerId &id) {
|
||||
PeersData::const_iterator i = peersData.constFind(id);
|
||||
PeerData *peer(const PeerId &id, PeerData::LoadedStatus restriction) {
|
||||
if (!id) return nullptr;
|
||||
|
||||
auto i = peersData.constFind(id);
|
||||
if (i == peersData.cend()) {
|
||||
PeerData *newData = 0;
|
||||
PeerData *newData = nullptr;
|
||||
if (peerIsUser(id)) {
|
||||
newData = new UserData(id);
|
||||
} else if (peerIsChat(id)) {
|
||||
|
@ -1417,33 +1424,26 @@ namespace App {
|
|||
} else if (peerIsChannel(id)) {
|
||||
newData = new ChannelData(id);
|
||||
}
|
||||
if (!newData) return 0;
|
||||
t_assert(newData != nullptr);
|
||||
|
||||
newData->input = MTPinputPeer(MTP_inputPeerEmpty());
|
||||
i = peersData.insert(id, newData);
|
||||
}
|
||||
switch (restriction) {
|
||||
case PeerData::MinimalLoaded: {
|
||||
if (i.value()->loadedStatus == PeerData::NotLoaded) {
|
||||
return nullptr;
|
||||
}
|
||||
} break;
|
||||
case PeerData::FullLoaded: {
|
||||
if (i.value()->loadedStatus != PeerData::FullLoaded) {
|
||||
return nullptr;
|
||||
}
|
||||
} break;
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
UserData *user(const PeerId &id) {
|
||||
return peer(id)->asUser();
|
||||
}
|
||||
ChatData *chat(const PeerId &id) {
|
||||
return peer(id)->asChat();
|
||||
}
|
||||
ChannelData *channel(const PeerId &id) {
|
||||
return peer(id)->asChannel();
|
||||
}
|
||||
UserData *user(int32 user_id) {
|
||||
return user(peerFromUser(user_id));
|
||||
}
|
||||
ChatData *chat(int32 chat_id) {
|
||||
return chat(peerFromChat(chat_id));
|
||||
}
|
||||
ChannelData *channel(int32 channel_id) {
|
||||
return channel(peerFromChannel(channel_id));
|
||||
}
|
||||
|
||||
UserData *self() {
|
||||
return ::self;
|
||||
}
|
||||
|
@ -1794,19 +1794,22 @@ namespace App {
|
|||
MsgsData *data = fetchMsgsData(item->channelId(), false);
|
||||
if (!data) return;
|
||||
|
||||
MsgsData::iterator i = data->find(item->id);
|
||||
auto i = data->find(item->id);
|
||||
if (i != data->cend()) {
|
||||
if (i.value() == item) {
|
||||
data->erase(i);
|
||||
}
|
||||
}
|
||||
historyItemDetached(item);
|
||||
DependentItems::iterator j = ::dependentItems.find(item);
|
||||
auto j = ::dependentItems.find(item);
|
||||
if (j != ::dependentItems.cend()) {
|
||||
for (OrderedSet<HistoryItem*>::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) {
|
||||
k.key()->dependencyItemRemoved(item);
|
||||
}
|
||||
DependentItemsSet items;
|
||||
std::swap(items, j.value());
|
||||
::dependentItems.erase(j);
|
||||
|
||||
for_const (HistoryItem *dependent, items) {
|
||||
dependent->dependencyItemRemoved(item);
|
||||
}
|
||||
}
|
||||
if (App::main() && !App::quitting()) {
|
||||
App::main()->itemRemoved(item);
|
||||
|
@ -1816,8 +1819,8 @@ namespace App {
|
|||
void historyUpdateDependent(HistoryItem *item) {
|
||||
DependentItems::iterator j = ::dependentItems.find(item);
|
||||
if (j != ::dependentItems.cend()) {
|
||||
for (OrderedSet<HistoryItem*>::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) {
|
||||
k.key()->updateDependencyItem();
|
||||
for_const (HistoryItem *dependent, j.value()) {
|
||||
dependent->updateDependencyItem();
|
||||
}
|
||||
}
|
||||
if (App::main()) {
|
||||
|
@ -1829,15 +1832,15 @@ namespace App {
|
|||
::dependentItems.clear();
|
||||
|
||||
QVector<HistoryItem*> toDelete;
|
||||
for (MsgsData::const_iterator i = msgsData.cbegin(), e = msgsData.cend(); i != e; ++i) {
|
||||
if ((*i)->detached()) {
|
||||
toDelete.push_back(*i);
|
||||
for_const (HistoryItem *item, msgsData) {
|
||||
if (item->detached()) {
|
||||
toDelete.push_back(item);
|
||||
}
|
||||
}
|
||||
for (ChannelMsgsData::const_iterator j = channelMsgsData.cbegin(), end = channelMsgsData.cend(); j != end; ++j) {
|
||||
for (MsgsData::const_iterator i = j->cbegin(), e = j->cend(); i != e; ++i) {
|
||||
if ((*i)->detached()) {
|
||||
toDelete.push_back(*i);
|
||||
for_const (const MsgsData &chMsgsData, channelMsgsData) {
|
||||
for_const (HistoryItem *item, chMsgsData) {
|
||||
if (item->detached()) {
|
||||
toDelete.push_back(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1878,9 +1881,9 @@ namespace App {
|
|||
webPagesData.clear();
|
||||
if (api()) api()->clearWebPageRequests();
|
||||
cSetRecentStickers(RecentStickerPack());
|
||||
cSetStickerSets(StickerSets());
|
||||
cSetStickerSetsOrder(StickerSetsOrder());
|
||||
cSetLastStickersUpdate(0);
|
||||
Global::SetStickerSets(Stickers::Sets());
|
||||
Global::SetStickerSetsOrder(Stickers::Order());
|
||||
Global::SetLastStickersUpdate(0);
|
||||
cSetSavedGifs(SavedGifs());
|
||||
cSetLastSavedGifsUpdate(0);
|
||||
cSetReportSpamStatuses(ReportSpamStatuses());
|
||||
|
@ -1903,7 +1906,7 @@ namespace App {
|
|||
}
|
||||
|
||||
void historyUnregDependency(HistoryItem *dependent, HistoryItem *dependency) {
|
||||
DependentItems::iterator i = ::dependentItems.find(dependency);
|
||||
auto i = ::dependentItems.find(dependency);
|
||||
if (i != ::dependentItems.cend()) {
|
||||
i.value().remove(dependent);
|
||||
if (i.value().isEmpty()) {
|
||||
|
@ -2446,13 +2449,13 @@ namespace App {
|
|||
case mtpc_replyKeyboardHide: {
|
||||
const MTPDreplyKeyboardHide &d(markup.c_replyKeyboardHide());
|
||||
if (d.vflags.v) {
|
||||
insertReplyMarkup(channelId, msgId, ReplyMarkup(d.vflags.v | MTPDreplyKeyboardMarkup_flag_ZERO));
|
||||
insertReplyMarkup(channelId, msgId, ReplyMarkup(mtpCastFlags(d.vflags.v) | MTPDreplyKeyboardMarkup_ClientFlag::f_zero));
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_replyKeyboardForceReply: {
|
||||
const MTPDreplyKeyboardForceReply &d(markup.c_replyKeyboardForceReply());
|
||||
insertReplyMarkup(channelId, msgId, ReplyMarkup(d.vflags.v | MTPDreplyKeyboardMarkup_flag_FORCE_REPLY));
|
||||
insertReplyMarkup(channelId, msgId, ReplyMarkup(mtpCastFlags(d.vflags.v) | MTPDreplyKeyboardMarkup_ClientFlag::f_force_reply));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
@ -2486,9 +2489,12 @@ namespace App {
|
|||
}
|
||||
|
||||
void setProxySettings(QNetworkAccessManager &manager) {
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
manager.setProxy(getHttpProxySettings());
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
QNetworkProxy getHttpProxySettings() {
|
||||
const ConnectionProxy *proxy = 0;
|
||||
if (Global::started()) {
|
||||
|
@ -2501,14 +2507,17 @@ namespace App {
|
|||
}
|
||||
return QNetworkProxy(QNetworkProxy::DefaultProxy);
|
||||
}
|
||||
#endif
|
||||
|
||||
void setProxySettings(QTcpSocket &socket) {
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
if (cConnectionType() == dbictTcpProxy) {
|
||||
const ConnectionProxy &p(cConnectionProxy());
|
||||
socket.setProxy(QNetworkProxy(QNetworkProxy::Socks5Proxy, p.host, p.port, p.user, p.password));
|
||||
} else {
|
||||
socket.setProxy(QNetworkProxy(QNetworkProxy::NoProxy));
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
QImage **cornersMask() {
|
||||
|
|
|
@ -45,11 +45,11 @@ typedef QHash<PhotoId, PhotoData*> PhotosData;
|
|||
typedef QHash<DocumentId, DocumentData*> DocumentsData;
|
||||
|
||||
struct ReplyMarkup {
|
||||
ReplyMarkup(int32 flags = 0) : flags(flags) {
|
||||
ReplyMarkup(MTPDreplyKeyboardMarkup::Flags flags = 0) : flags(flags) {
|
||||
}
|
||||
typedef QList<QList<QString> > Commands;
|
||||
Commands commands;
|
||||
int32 flags;
|
||||
MTPDreplyKeyboardMarkup::Flags flags;
|
||||
};
|
||||
|
||||
class LayeredWidget;
|
||||
|
@ -110,21 +110,47 @@ namespace App {
|
|||
WebPageData *feedWebPage(const MTPDwebPagePending &webpage, WebPageData *convert = 0);
|
||||
WebPageData *feedWebPage(const MTPWebPage &webpage);
|
||||
|
||||
PeerData *peerLoaded(const PeerId &id);
|
||||
UserData *userLoaded(const PeerId &id);
|
||||
ChatData *chatLoaded(const PeerId &id);
|
||||
ChannelData *channelLoaded(const PeerId &id);
|
||||
UserData *userLoaded(int32 user);
|
||||
ChatData *chatLoaded(int32 chat);
|
||||
ChannelData *channelLoaded(int32 channel);
|
||||
PeerData *peer(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded);
|
||||
inline UserData *user(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
|
||||
return asUser(peer(id, restriction));
|
||||
}
|
||||
inline ChatData *chat(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
|
||||
return asChat(peer(id, restriction));
|
||||
}
|
||||
inline ChannelData *channel(const PeerId &id, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
|
||||
return asChannel(peer(id, restriction));
|
||||
}
|
||||
inline UserData *user(UserId userId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
|
||||
return asUser(peer(peerFromUser(userId), restriction));
|
||||
}
|
||||
inline ChatData *chat(ChatId chatId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
|
||||
return asChat(peer(peerFromChat(chatId), restriction));
|
||||
}
|
||||
inline ChannelData *channel(ChannelId channelId, PeerData::LoadedStatus restriction = PeerData::NotLoaded) {
|
||||
return asChannel(peer(peerFromChannel(channelId), restriction));
|
||||
}
|
||||
inline PeerData *peerLoaded(const PeerId &id) {
|
||||
return peer(id, PeerData::FullLoaded);
|
||||
}
|
||||
inline UserData *userLoaded(const PeerId &id) {
|
||||
return user(id, PeerData::FullLoaded);
|
||||
}
|
||||
inline ChatData *chatLoaded(const PeerId &id) {
|
||||
return chat(id, PeerData::FullLoaded);
|
||||
}
|
||||
inline ChannelData *channelLoaded(const PeerId &id) {
|
||||
return channel(id, PeerData::FullLoaded);
|
||||
}
|
||||
inline UserData *userLoaded(UserId userId) {
|
||||
return user(userId, PeerData::FullLoaded);
|
||||
}
|
||||
inline ChatData *chatLoaded(ChatId chatId) {
|
||||
return chat(chatId, PeerData::FullLoaded);
|
||||
}
|
||||
inline ChannelData *channelLoaded(ChannelId channelId) {
|
||||
return channel(channelId, PeerData::FullLoaded);
|
||||
}
|
||||
|
||||
PeerData *peer(const PeerId &id);
|
||||
UserData *user(const PeerId &id);
|
||||
ChatData *chat(const PeerId &id);
|
||||
ChannelData *channel(const PeerId &id);
|
||||
UserData *user(int32 user_id);
|
||||
ChatData *chat(int32 chat_id);
|
||||
ChannelData *channel(int32 channel_id);
|
||||
UserData *self();
|
||||
PeerData *peerByName(const QString &username);
|
||||
QString peerName(const PeerData *peer, bool forDialogs = false);
|
||||
|
@ -145,10 +171,11 @@ namespace App {
|
|||
History *historyLoaded(const PeerId &peer);
|
||||
HistoryItem *histItemById(ChannelId channelId, MsgId itemId);
|
||||
inline History *history(const PeerData *peer) {
|
||||
t_assert(peer != nullptr);
|
||||
return history(peer->id);
|
||||
}
|
||||
inline History *historyLoaded(const PeerData *peer) {
|
||||
return historyLoaded(peer->id);
|
||||
return peer ? historyLoaded(peer->id) : nullptr;
|
||||
}
|
||||
inline HistoryItem *histItemById(const ChannelData *channel, MsgId itemId) {
|
||||
return histItemById(channel ? peerToChannel(channel->id) : 0, itemId);
|
||||
|
@ -250,7 +277,9 @@ namespace App {
|
|||
const ReplyMarkup &replyMarkup(ChannelId channelId, MsgId msgId);
|
||||
|
||||
void setProxySettings(QNetworkAccessManager &manager);
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
QNetworkProxy getHttpProxySettings();
|
||||
#endif
|
||||
void setProxySettings(QTcpSocket &socket);
|
||||
|
||||
QImage **cornersMask();
|
||||
|
|
|
@ -120,16 +120,16 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv)
|
|||
#endif
|
||||
|
||||
if (cManyInstance()) {
|
||||
LOG(("Many instance allowed, starting.."));
|
||||
LOG(("Many instance allowed, starting..."));
|
||||
singleInstanceChecked();
|
||||
} else {
|
||||
LOG(("Connecting local socket to %1..").arg(_localServerName));
|
||||
LOG(("Connecting local socket to %1...").arg(_localServerName));
|
||||
_localSocket.connectToServer(_localServerName);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::socketConnected() {
|
||||
LOG(("Socket connected, this is not the first application instance, sending show command.."));
|
||||
LOG(("Socket connected, this is not the first application instance, sending show command..."));
|
||||
_secondInstance = true;
|
||||
|
||||
QString commands;
|
||||
|
@ -154,7 +154,7 @@ void Application::socketWritten(qint64/* bytes*/) {
|
|||
if (_localSocket.bytesToWrite()) {
|
||||
return;
|
||||
}
|
||||
LOG(("Show command written, waiting response.."));
|
||||
LOG(("Show command written, waiting response..."));
|
||||
}
|
||||
|
||||
void Application::socketReading() {
|
||||
|
@ -166,7 +166,7 @@ void Application::socketReading() {
|
|||
if (QRegularExpression("RES:(\\d+);").match(_localSocketReadData).hasMatch()) {
|
||||
uint64 pid = _localSocketReadData.mid(4, _localSocketReadData.length() - 5).toULongLong();
|
||||
psActivateProcess(pid);
|
||||
LOG(("Show command response received, pid = %1, activating and quitting..").arg(pid));
|
||||
LOG(("Show command response received, pid = %1, activating and quitting...").arg(pid));
|
||||
return App::quit();
|
||||
}
|
||||
}
|
||||
|
@ -175,14 +175,14 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
|
|||
if (App::quitting()) return;
|
||||
|
||||
if (_secondInstance) {
|
||||
LOG(("Could not write show command, error %1, quitting..").arg(e));
|
||||
LOG(("Could not write show command, error %1, quitting...").arg(e));
|
||||
return App::quit();
|
||||
}
|
||||
|
||||
if (e == QLocalSocket::ServerNotFoundError) {
|
||||
LOG(("This is the only instance of Telegram, starting server and app.."));
|
||||
LOG(("This is the only instance of Telegram, starting server and app..."));
|
||||
} else {
|
||||
LOG(("Socket connect error %1, starting server and app..").arg(e));
|
||||
LOG(("Socket connect error %1, starting server and app...").arg(e));
|
||||
}
|
||||
_localSocket.close();
|
||||
|
||||
|
@ -196,7 +196,7 @@ void Application::socketError(QLocalSocket::LocalSocketError e) {
|
|||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
if (!cNoStartUpdate() && checkReadyUpdate()) {
|
||||
cSetRestartingUpdate(true);
|
||||
DEBUG_LOG(("Application Info: installing update instead of starting app.."));
|
||||
DEBUG_LOG(("Application Info: installing update instead of starting app..."));
|
||||
return App::quit();
|
||||
}
|
||||
#endif
|
||||
|
@ -235,7 +235,7 @@ void Application::singleInstanceChecked() {
|
|||
|
||||
void Application::socketDisconnected() {
|
||||
if (_secondInstance) {
|
||||
DEBUG_LOG(("Application Error: socket disconnected before command response received, quitting.."));
|
||||
DEBUG_LOG(("Application Error: socket disconnected before command response received, quitting..."));
|
||||
return App::quit();
|
||||
}
|
||||
}
|
||||
|
@ -704,7 +704,7 @@ AppClass::AppClass() : QObject()
|
|||
anim::startManager();
|
||||
historyInit();
|
||||
|
||||
DEBUG_LOG(("Application Info: inited.."));
|
||||
DEBUG_LOG(("Application Info: inited..."));
|
||||
|
||||
application()->installNativeEventFilter(psNativeEventFilter());
|
||||
|
||||
|
@ -714,7 +714,7 @@ AppClass::AppClass() : QObject()
|
|||
|
||||
connect(&killDownloadSessionsTimer, SIGNAL(timeout()), this, SLOT(killDownloadSessions()));
|
||||
|
||||
DEBUG_LOG(("Application Info: starting app.."));
|
||||
DEBUG_LOG(("Application Info: starting app..."));
|
||||
|
||||
QMimeDatabase().mimeTypeForName(qsl("text/plain")); // create mime database
|
||||
|
||||
|
@ -724,7 +724,7 @@ AppClass::AppClass() : QObject()
|
|||
|
||||
Sandbox::connect(SIGNAL(applicationStateChanged(Qt::ApplicationState)), this, SLOT(onAppStateChanged(Qt::ApplicationState)));
|
||||
|
||||
DEBUG_LOG(("Application Info: window created.."));
|
||||
DEBUG_LOG(("Application Info: window created..."));
|
||||
|
||||
Shortcuts::start();
|
||||
|
||||
|
@ -734,16 +734,16 @@ AppClass::AppClass() : QObject()
|
|||
Local::ReadMapState state = Local::readMap(QByteArray());
|
||||
if (state == Local::ReadMapPassNeeded) {
|
||||
cSetHasPasscode(true);
|
||||
DEBUG_LOG(("Application Info: passcode nneded.."));
|
||||
DEBUG_LOG(("Application Info: passcode needed..."));
|
||||
} else {
|
||||
DEBUG_LOG(("Application Info: local map read.."));
|
||||
DEBUG_LOG(("Application Info: local map read..."));
|
||||
MTP::start();
|
||||
}
|
||||
|
||||
MTP::setStateChangedHandler(mtpStateChanged);
|
||||
MTP::setSessionResetHandler(mtpSessionReset);
|
||||
|
||||
DEBUG_LOG(("Application Info: MTP started.."));
|
||||
DEBUG_LOG(("Application Info: MTP started..."));
|
||||
|
||||
DEBUG_LOG(("Application Info: showing."));
|
||||
if (state == Local::ReadMapPassNeeded) {
|
||||
|
@ -761,7 +761,9 @@ AppClass::AppClass() : QObject()
|
|||
_window->showSettings();
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
#endif
|
||||
|
||||
if (state != Local::ReadMapPassNeeded) {
|
||||
checkMapVersion();
|
||||
|
@ -899,12 +901,16 @@ void AppClass::onAppStateChanged(Qt::ApplicationState state) {
|
|||
}
|
||||
}
|
||||
|
||||
void AppClass::call_handleHistoryUpdate() {
|
||||
Notify::handlePendingHistoryUpdate();
|
||||
}
|
||||
|
||||
void AppClass::killDownloadSessions() {
|
||||
uint64 ms = getms(), left = MTPAckSendWaiting + MTPKillFileSessionTimeout;
|
||||
for (QMap<int32, uint64>::iterator i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) {
|
||||
if (i.value() <= ms) {
|
||||
for (int j = 0; j < MTPDownloadSessionsCount; ++j) {
|
||||
MTP::stopSession(MTP::dld(j) + i.key());
|
||||
MTP::stopSession(MTP::dldDcId(i.key(), j));
|
||||
}
|
||||
i = killDownloadSessionTimes.erase(i);
|
||||
} else {
|
||||
|
@ -998,7 +1004,7 @@ void AppClass::uploadProfilePhoto(const QImage &tosend, const PeerId &peerId) {
|
|||
QBuffer jpegBuffer(&jpeg);
|
||||
full.save(&jpegBuffer, "JPG", 87);
|
||||
|
||||
PhotoId id = MTP::nonce<PhotoId>();
|
||||
PhotoId id = rand_value<PhotoId>();
|
||||
|
||||
MTPPhoto photo(MTP_photo(MTP_long(id), MTP_long(0), MTP_int(unixtime()), MTP_vector<MTPPhotoSize>(photoSizes)));
|
||||
|
||||
|
@ -1019,10 +1025,10 @@ void AppClass::checkMapVersion() {
|
|||
if (Local::oldMapVersion() < AppVersion) {
|
||||
if (Local::oldMapVersion()) {
|
||||
QString versionFeatures;
|
||||
if ((cDevVersion() || cBetaVersion()) && Local::oldMapVersion() < 9031) {
|
||||
if ((cDevVersion() || cBetaVersion()) && Local::oldMapVersion() < 9035) {
|
||||
// QString ctrl = (cPlatform() == dbipMac || cPlatform() == dbipMacOld) ? qsl("Cmd") : qsl("Ctrl");
|
||||
// versionFeatures = QString::fromUtf8("\xe2\x80\x94 %1+W or %2+F4 for close window\n\xe2\x80\x94 %3+L to lock Telegram if you use a local passcode\n\xe2\x80\x94 Bug fixes and other minor improvements").arg(ctrl).arg(ctrl).arg(ctrl);// .replace('@', qsl("@") + QChar(0x200D));
|
||||
versionFeatures = lng_new_version_text(lt_link, qsl("https://telegram.org/blog/supergroups5k")).trimmed();
|
||||
versionFeatures = QString::fromUtf8("\xe2\x80\x94 Design improvements\n\xe2\x80\x94 Bug fixes and other minor improvements");// .replace('@', qsl("@") + QChar(0x200D));
|
||||
// versionFeatures = lng_new_version_text(lt_link, qsl("https://telegram.org/blog/supergroups5k")).trimmed();
|
||||
} else if (Local::oldMapVersion() < 9031) {
|
||||
versionFeatures = lng_new_version_text(lt_link, qsl("https://telegram.org/blog/supergroups5k")).trimmed();
|
||||
} else {
|
||||
|
|
|
@ -200,6 +200,8 @@ public slots:
|
|||
void killDownloadSessions();
|
||||
void onAppStateChanged(Qt::ApplicationState state);
|
||||
|
||||
void call_handleHistoryUpdate();
|
||||
|
||||
private:
|
||||
|
||||
QMap<FullMsgId, PeerId> photoUpdates;
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 177 KiB After Width: | Height: | Size: 178 KiB |
Binary file not shown.
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 243 KiB |
|
@ -19,6 +19,7 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
#include <AL/al.h>
|
||||
|
@ -27,10 +28,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#define AL_ALEXT_PROTOTYPES
|
||||
#include <AL/alext.h>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswresample/swresample.h>
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include <iconv.h>
|
||||
|
||||
#undef iconv_open
|
||||
|
@ -46,10 +50,9 @@ size_t iconv (iconv_t cd, char* * inbuf, size_t *inbytesleft, char* * outbuf, s
|
|||
int iconv_close (iconv_t cd) {
|
||||
return libiconv_close(cd);
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
} // extern "C"
|
||||
|
||||
namespace {
|
||||
ALCdevice *audioDevice = 0;
|
||||
|
@ -224,12 +227,15 @@ void audioPlayNotify() {
|
|||
emit audioPlayer()->faderOnTimer();
|
||||
}
|
||||
|
||||
// can be called at any moment when audio error
|
||||
void audioFinish() {
|
||||
if (player) {
|
||||
delete player;
|
||||
player = nullptr;
|
||||
}
|
||||
if (capture) {
|
||||
delete capture;
|
||||
capture = nullptr;
|
||||
}
|
||||
|
||||
alSourceStop(notifySource);
|
||||
|
@ -243,14 +249,14 @@ void audioFinish() {
|
|||
}
|
||||
|
||||
if (audioContext) {
|
||||
alcMakeContextCurrent(NULL);
|
||||
alcMakeContextCurrent(nullptr);
|
||||
alcDestroyContext(audioContext);
|
||||
audioContext = 0;
|
||||
audioContext = nullptr;
|
||||
}
|
||||
|
||||
if (audioDevice) {
|
||||
alcCloseDevice(audioDevice);
|
||||
audioDevice = 0;
|
||||
audioDevice = nullptr;
|
||||
}
|
||||
|
||||
cSetHasAudioCapture(false);
|
||||
|
@ -1685,7 +1691,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
|
|||
err = SetupErrorAtStart;
|
||||
QMutexLocker lock(&playerMutex);
|
||||
AudioPlayer *voice = audioPlayer();
|
||||
if (!voice) return 0;
|
||||
if (!voice) return nullptr;
|
||||
|
||||
bool isGoodId = false;
|
||||
AudioPlayer::Msg *m = 0;
|
||||
|
@ -1717,7 +1723,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
|
|||
if (!l || !m) {
|
||||
LOG(("Audio Error: trying to load part of audio, that is not current at the moment"));
|
||||
err = SetupErrorNotPlaying;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (*l && (!isGoodId || !(*l)->check(m->file, m->data))) {
|
||||
|
@ -1741,27 +1747,26 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
|
|||
// if (!f.open(QIODevice::ReadOnly)) {
|
||||
// LOG(("Audio Error: could not open file '%1'").arg(m->fname));
|
||||
// m->state = AudioPlayerStoppedAtStart;
|
||||
// return 0;
|
||||
// return nullptr;
|
||||
// }
|
||||
// header = f.read(8);
|
||||
// }
|
||||
// if (header.size() < 8) {
|
||||
// LOG(("Audio Error: could not read header from file '%1', data size %2").arg(m->fname).arg(m->data.isEmpty() ? QFileInfo(m->fname).size() : m->data.size()));
|
||||
// m->state = AudioPlayerStoppedAtStart;
|
||||
// return 0;
|
||||
// return nullptr;
|
||||
// }
|
||||
|
||||
*l = new FFMpegLoader(m->file, m->data);
|
||||
|
||||
int ret;
|
||||
if (!(*l)->open(position)) {
|
||||
m->state = AudioPlayerStoppedAtStart;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
int64 duration = (*l)->duration();
|
||||
if (duration <= 0) {
|
||||
m->state = AudioPlayerStoppedAtStart;
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
m->duration = duration;
|
||||
m->frequency = (*l)->frequency();
|
||||
|
@ -1771,7 +1776,7 @@ AudioPlayerLoader *AudioPlayerLoaders::setupLoader(MediaOverviewType type, const
|
|||
if (!m->skipEnd) {
|
||||
err = SetupErrorLoadedFull;
|
||||
LOG(("Audio Error: trying to load part of audio, that is already loaded to the end"));
|
||||
return 0;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return *l;
|
||||
|
@ -2029,7 +2034,7 @@ void AudioCaptureInner::onStart() {
|
|||
}
|
||||
|
||||
// Open audio stream
|
||||
if ((res = avcodec_open2(d->codecContext, d->codec, NULL)) < 0) {
|
||||
if ((res = avcodec_open2(d->codecContext, d->codec, nullptr)) < 0) {
|
||||
LOG(("Audio Error: Unable to avcodec_open2 for capture, error %1, %2").arg(res).arg(av_make_error_string(err, sizeof(err), res)));
|
||||
onStop(false);
|
||||
emit error();
|
||||
|
@ -2505,7 +2510,7 @@ MTPDocumentAttribute audioReadSongAttributes(const QString &fname, const QByteAr
|
|||
cover = reader.cover();
|
||||
coverBytes = reader.coverBytes();
|
||||
coverFormat = reader.coverFormat();
|
||||
return MTP_documentAttributeAudio(MTP_int(MTPDdocumentAttributeAudio::flag_title | MTPDdocumentAttributeAudio::flag_performer), MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer()), MTPstring());
|
||||
return MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_title | MTPDdocumentAttributeAudio::Flag::f_performer), MTP_int(duration), MTP_string(reader.title()), MTP_string(reader.performer()), MTPstring());
|
||||
}
|
||||
}
|
||||
return MTP_documentAttributeFilename(MTP_string(fname));
|
||||
|
|
|
@ -20,9 +20,22 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "autoupdater.h"
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
#ifdef Q_OS_WIN // use Lzma SDK for win
|
||||
#include <LzmaLib.h>
|
||||
#else // Q_OS_WIN
|
||||
#include <lzma.h>
|
||||
#endif // else of Q_OS_WIN
|
||||
|
||||
#include "application.h"
|
||||
#include "pspecific.h"
|
||||
#include "autoupdater.h"
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
|
||||
|
@ -51,7 +64,7 @@ void UpdateChecker::initOutput() {
|
|||
fileName = m.captured(1).replace(QRegularExpression(qsl("[^a-zA-Z0-9_\\-]")), QString());
|
||||
}
|
||||
if (fileName.isEmpty()) {
|
||||
fileName = qsl("tupdate-%1").arg(MTP::nonce<uint32>() % 1000000);
|
||||
fileName = qsl("tupdate-%1").arg(rand_value<uint32>() % 1000000);
|
||||
}
|
||||
QString dirStr = cWorkingDir() + qsl("tupdates/");
|
||||
fileName = dirStr + fileName;
|
||||
|
@ -556,7 +569,7 @@ bool checkReadyUpdate() {
|
|||
}
|
||||
#elif defined Q_OS_MAC
|
||||
QDir().mkpath(QFileInfo(curUpdater).absolutePath());
|
||||
DEBUG_LOG(("Update Info: moving %1 to %2..").arg(updater.absoluteFilePath()).arg(curUpdater));
|
||||
DEBUG_LOG(("Update Info: moving %1 to %2...").arg(updater.absoluteFilePath()).arg(curUpdater));
|
||||
if (!objc_moveFile(updater.absoluteFilePath(), curUpdater)) {
|
||||
UpdateChecker::clearAll();
|
||||
return false;
|
||||
|
|
|
@ -107,6 +107,7 @@ void AboutBox::paintEvent(QPaintEvent *e) {
|
|||
paintTitle(p, qsl("Telegram Desktop"));
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
QString _getCrashReportFile(const QMimeData *m) {
|
||||
if (!m || m->urls().size() != 1) return QString();
|
||||
|
||||
|
@ -115,19 +116,24 @@ QString _getCrashReportFile(const QMimeData *m) {
|
|||
|
||||
return file.endsWith(qstr(".telegramcrash"), Qt::CaseInsensitive) ? file : QString();
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
void AboutBox::dragEnterEvent(QDragEnterEvent *e) {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
if (!_getCrashReportFile(e->mimeData()).isEmpty()) {
|
||||
e->setDropAction(Qt::CopyAction);
|
||||
e->accept();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AboutBox::dropEvent(QDropEvent *e) {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
if (!_getCrashReportFile(e->mimeData()).isEmpty()) {
|
||||
e->acceptProposedAction();
|
||||
showCrashReportWindow(_getCrashReportFile(e->mimeData()));
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
QString telegramFaqLink() {
|
||||
|
|
|
@ -188,11 +188,11 @@ void AddContactBox::onSave() {
|
|||
}
|
||||
_sentName = firstName;
|
||||
if (_user) {
|
||||
_contactId = MTP::nonce<uint64>();
|
||||
_contactId = rand_value<uint64>();
|
||||
QVector<MTPInputContact> v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(_user->phone), MTP_string(firstName), MTP_string(lastName)));
|
||||
_addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector<MTPInputContact>(v), MTP_bool(false)), rpcDone(&AddContactBox::onSaveUserDone), rpcFail(&AddContactBox::onSaveUserFail));
|
||||
} else {
|
||||
_contactId = MTP::nonce<uint64>();
|
||||
_contactId = rand_value<uint64>();
|
||||
QVector<MTPInputContact> v(1, MTP_inputPhoneContact(MTP_long(_contactId), MTP_string(phone), MTP_string(firstName), MTP_string(lastName)));
|
||||
_addRequest = MTP::send(MTPcontacts_ImportContacts(MTP_vector<MTPInputContact>(v), MTP_bool(false)), rpcDone(&AddContactBox::onImportDone));
|
||||
}
|
||||
|
@ -224,18 +224,15 @@ void AddContactBox::onImportDone(const MTPcontacts_ImportedContacts &res) {
|
|||
App::feedUsers(d.vusers);
|
||||
|
||||
const QVector<MTPImportedContact> &v(d.vimported.c_vector().v);
|
||||
int32 uid = 0;
|
||||
UserData *user = nullptr;
|
||||
if (!v.isEmpty()) {
|
||||
const MTPDimportedContact &c(v.front().c_importedContact());
|
||||
if (c.vclient_id.v != _contactId) return;
|
||||
|
||||
uid = c.vuser_id.v;
|
||||
if (uid && !App::userLoaded(uid)) {
|
||||
uid = 0;
|
||||
}
|
||||
user = App::userLoaded(c.vuser_id.v);
|
||||
}
|
||||
if (uid) {
|
||||
Notify::userIsContactChanged(App::userLoaded(peerFromUser(uid)), true);
|
||||
if (user) {
|
||||
Notify::userIsContactChanged(user, true);
|
||||
Ui::hideLayer();
|
||||
} else {
|
||||
_save.hide();
|
||||
|
@ -499,8 +496,8 @@ void GroupInfoBox::onNext() {
|
|||
Ui::showLayer(new ContactsBox(title, _photoBig), KeepOtherLayers);
|
||||
} else {
|
||||
bool mega = false;
|
||||
int32 flags = mega ? MTPchannels_CreateChannel::flag_megagroup : MTPchannels_CreateChannel::flag_broadcast;
|
||||
_creationRequestId = MTP::send(MTPchannels_CreateChannel(MTP_int(flags), MTP_string(title), MTP_string(description)), rpcDone(&GroupInfoBox::creationDone), rpcFail(&GroupInfoBox::creationFail));
|
||||
MTPchannels_CreateChannel::Flags flags = mega ? MTPchannels_CreateChannel::Flag::f_megagroup : MTPchannels_CreateChannel::Flag::f_broadcast;
|
||||
_creationRequestId = MTP::send(MTPchannels_CreateChannel(MTP_flags(flags), MTP_string(title), MTP_string(description)), rpcDone(&GroupInfoBox::creationDone), rpcFail(&GroupInfoBox::creationFail));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,6 +537,9 @@ bool GroupInfoBox::creationFail(const RPCError &error) {
|
|||
_title.setFocus();
|
||||
_title.showError();
|
||||
return true;
|
||||
} else if (error.type() == qstr("USER_RESTRICTED")) {
|
||||
Ui::showLayer(new InformBox(lang(lng_cant_do_this)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -839,7 +839,7 @@ void SetupChannelBox::onChange() {
|
|||
}
|
||||
_checkTimer.stop();
|
||||
} else {
|
||||
int32 i, len = name.size();
|
||||
int32 len = name.size();
|
||||
for (int32 i = 0; i < len; ++i) {
|
||||
QChar ch = name.at(i);
|
||||
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_') {
|
||||
|
@ -1115,8 +1115,8 @@ void EditNameTitleBox::onSave() {
|
|||
}
|
||||
_sentName = first;
|
||||
if (_peer == App::self()) {
|
||||
int32 flags = MTPaccount_UpdateProfile::flag_first_name | MTPaccount_UpdateProfile::flag_last_name;
|
||||
_requestId = MTP::send(MTPaccount_UpdateProfile(MTP_int(flags), MTP_string(first), MTP_string(last), MTPstring()), rpcDone(&EditNameTitleBox::onSaveSelfDone), rpcFail(&EditNameTitleBox::onSaveSelfFail));
|
||||
MTPaccount_UpdateProfile::Flags flags = MTPaccount_UpdateProfile::Flag::f_first_name | MTPaccount_UpdateProfile::Flag::f_last_name;
|
||||
_requestId = MTP::send(MTPaccount_UpdateProfile(MTP_flags(flags), MTP_string(first), MTP_string(last), MTPstring()), rpcDone(&EditNameTitleBox::onSaveSelfDone), rpcFail(&EditNameTitleBox::onSaveSelfFail));
|
||||
} else if (_peer->isChat()) {
|
||||
_requestId = MTP::send(MTPmessages_EditChatTitle(_peer->asChat()->inputChat, MTP_string(first)), rpcDone(&EditNameTitleBox::onSaveChatDone), rpcFail(&EditNameTitleBox::onSaveChatFail));
|
||||
}
|
||||
|
|
|
@ -404,8 +404,11 @@ void PinMessageBox::resizeEvent(QResizeEvent *e) {
|
|||
void PinMessageBox::onPin() {
|
||||
if (_requestId) return;
|
||||
|
||||
int32 flags = _notify.checked() ? 0 : MTPchannels_UpdatePinnedMessage::flag_silent;
|
||||
_requestId = MTP::send(MTPchannels_UpdatePinnedMessage(MTP_int(flags), _channel->inputChannel, MTP_int(_msgId)), rpcDone(&PinMessageBox::pinDone), rpcFail(&PinMessageBox::pinFail));
|
||||
MTPchannels_UpdatePinnedMessage::Flags flags = 0;
|
||||
if (_notify.checked()) {
|
||||
flags |= MTPchannels_UpdatePinnedMessage::Flag::f_silent;
|
||||
}
|
||||
_requestId = MTP::send(MTPchannels_UpdatePinnedMessage(MTP_flags(flags), _channel->inputChannel, MTP_int(_msgId)), rpcDone(&PinMessageBox::pinDone), rpcFail(&PinMessageBox::pinFail));
|
||||
}
|
||||
|
||||
void PinMessageBox::showAll() {
|
||||
|
@ -473,7 +476,7 @@ void RichDeleteMessageBox::onDelete() {
|
|||
if (_deleteAll.checked()) {
|
||||
App::main()->deleteAllFromUser(_channel, _from);
|
||||
}
|
||||
if (auto item = App::histItemById(_channel ? peerToChannel(_channel->id) : 0, _msgId)) {
|
||||
if (HistoryItem *item = App::histItemById(_channel ? peerToChannel(_channel->id) : 0, _msgId)) {
|
||||
bool wasLast = (item->history()->lastMsg == item);
|
||||
item->destroy();
|
||||
if (_msgId > 0) {
|
||||
|
@ -482,7 +485,6 @@ void RichDeleteMessageBox::onDelete() {
|
|||
App::main()->checkPeerHistory(_channel);
|
||||
}
|
||||
}
|
||||
Notify::historyItemsResized();
|
||||
Ui::hideLayer();
|
||||
}
|
||||
|
||||
|
|
|
@ -201,8 +201,10 @@ void ConnectionBox::onSave() {
|
|||
} else {
|
||||
cSetConnectionType(dbictAuto);
|
||||
cSetConnectionProxy(ConnectionProxy());
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(false);
|
||||
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||
#endif
|
||||
}
|
||||
if (cPlatform() == dbipWindows && cTryIPv6() != _tryIPv6.checked()) {
|
||||
cSetTryIPv6(_tryIPv6.checked());
|
||||
|
|
|
@ -224,7 +224,7 @@ void ContactsInner::onPeerNameChanged(PeerData *peer, const PeerData::Names &old
|
|||
|
||||
void ContactsInner::onAddBot() {
|
||||
if (_bot->botInfo && !_bot->botInfo->startGroupToken.isEmpty()) {
|
||||
MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(MTP::nonce<uint64>()), MTP_string(_bot->botInfo->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot));
|
||||
MTP::send(MTPmessages_StartBot(_bot->inputUser, _addToPeer->input, MTP_long(rand_value<uint64>()), MTP_string(_bot->botInfo->startGroupToken)), App::main()->rpcDone(&MainWidget::sentUpdatesReceived), App::main()->rpcFail(&MainWidget::addParticipantFail, _bot));
|
||||
} else {
|
||||
App::main()->addParticipants(_addToPeer, QVector<UserData*>(1, _bot));
|
||||
}
|
||||
|
@ -284,6 +284,8 @@ bool ContactsInner::addAdminFail(const RPCError &error, mtpRequestId req) {
|
|||
Ui::showLayer(new MaxInviteBox(_channel->invitationUrl), KeepOtherLayers);
|
||||
} else if (error.type() == "ADMINS_TOO_MUCH") {
|
||||
Ui::showLayer(new InformBox(lang(lng_channel_admins_too_much)), KeepOtherLayers);
|
||||
} else if (error.type() == qstr("USER_RESTRICTED")) {
|
||||
Ui::showLayer(new InformBox(lang(lng_cant_do_this)), KeepOtherLayers);
|
||||
} else {
|
||||
emit adminAdded();
|
||||
}
|
||||
|
@ -362,7 +364,7 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) {
|
|||
preloadFrom != _contacts->list.end && (_newItemHeight + preloadFrom->pos * _rowHeight) < yTo;
|
||||
preloadFrom = preloadFrom->next
|
||||
) {
|
||||
preloadFrom->history->peer->photo->load();
|
||||
preloadFrom->history->peer->loadUserpic();
|
||||
}
|
||||
}
|
||||
} else if (!_filtered.isEmpty()) {
|
||||
|
@ -373,7 +375,7 @@ void ContactsInner::loadProfilePhotos(int32 yFrom) {
|
|||
if (to > _filtered.size()) to = _filtered.size();
|
||||
|
||||
for (; from < to; ++from) {
|
||||
_filtered[from]->history->peer->photo->load();
|
||||
_filtered[from]->history->peer->loadUserpic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -445,7 +447,7 @@ void ContactsInner::paintDialog(Painter &p, PeerData *peer, ContactData *data, b
|
|||
}
|
||||
p.fillRect(0, 0, width(), _rowHeight, inverse ? st::contactsBgActive : (sel ? st::contactsBgOver : st::white));
|
||||
p.setPen(inverse ? st::white : st::black);
|
||||
p.drawPixmapLeft(st::contactsPadding.left(), st::contactsPadding.top(), width(), peer->photo->pix(st::contactsPhotoSize));
|
||||
peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width());
|
||||
|
||||
int32 namex = st::contactsPadding.left() + st::contactsPhotoSize + st::contactsPadding.left();
|
||||
int32 iconw = (_chat || _creating != CreatingGroupNone) ? (st::contactsCheckPosition.x() * 2 + st::contactsCheckIcon.pxWidth()) : 0;
|
||||
|
@ -783,7 +785,9 @@ void ContactsInner::changeCheckState(ContactData *data, PeerData *peer) {
|
|||
data->check = true;
|
||||
_checkedContacts.insert(peer, true);
|
||||
++_selCount;
|
||||
} else if ((!_channel || !_channel->isMegagroup()) && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) {
|
||||
} else if (_channel && !_channel->isMegagroup()) {
|
||||
Ui::showLayer(new MaxInviteBox(_channel->invitationUrl), KeepOtherLayers);
|
||||
} else if (!_channel && selectedCount() >= Global::ChatSizeMax() && selectedCount() < Global::MegagroupSizeMax()) {
|
||||
Ui::showLayer(new InformBox(lng_profile_add_more_after_upgrade(lt_count, Global::MegagroupSizeMax())), KeepOtherLayers);
|
||||
}
|
||||
if (cnt != _selCount) emit chosenChanged();
|
||||
|
@ -1549,7 +1553,7 @@ void ContactsBox::paintEvent(QPaintEvent *e) {
|
|||
paintTitle(p, lang(lng_channel_admins));
|
||||
} else if (_inner.chat() || _inner.creating() != CreatingGroupNone) {
|
||||
QString title(lang(addingAdmin ? lng_channel_add_admin : lng_profile_add_participant));
|
||||
QString additional(addingAdmin ? QString() : QString("%1 / %2").arg(_inner.selectedCount()).arg(Global::MegagroupSizeMax()));
|
||||
QString additional((addingAdmin || (_inner.channel() && !_inner.channel()->isMegagroup())) ? QString() : QString("%1 / %2").arg(_inner.selectedCount()).arg(Global::MegagroupSizeMax()));
|
||||
paintTitle(p, title, additional);
|
||||
} else if (_inner.bot()) {
|
||||
paintTitle(p, lang(lng_bot_choose_group));
|
||||
|
@ -1666,12 +1670,15 @@ void ContactsBox::getAdminsDone(const MTPmessages_ChatFull &result) {
|
|||
}
|
||||
}
|
||||
_saveRequestId = 0;
|
||||
for (ChatData::Admins::const_iterator i = curadmins.cbegin(), e = curadmins.cend(); i != e; ++i) {
|
||||
MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, i.key()->inputUser, MTP_boolFalse()), rpcDone(&ContactsBox::removeAdminDone, i.key()), rpcFail(&ContactsBox::editAdminFail), 0, (appoint.isEmpty() && i + 1 == e) ? 0 : 10);
|
||||
|
||||
for_const (UserData *user, curadmins) {
|
||||
MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, user->inputUser, MTP_boolFalse()), rpcDone(&ContactsBox::removeAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10);
|
||||
}
|
||||
for (int32 i = 0, l = appoint.size(); i < l; ++i) {
|
||||
MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, appoint.at(i)->inputUser, MTP_boolTrue()), rpcDone(&ContactsBox::setAdminDone, appoint.at(i)), rpcFail(&ContactsBox::editAdminFail), 0, (i + 1 == l) ? 0 : 10);
|
||||
for_const (UserData *user, appoint) {
|
||||
MTP::send(MTPmessages_EditChatAdmin(_inner.chat()->inputChat, user->inputUser, MTP_boolTrue()), rpcDone(&ContactsBox::setAdminDone, user), rpcFail(&ContactsBox::editAdminFail), 0, 10);
|
||||
}
|
||||
MTP::sendAnything();
|
||||
|
||||
_saveRequestId = curadmins.size() + appoint.size();
|
||||
if (!_saveRequestId) {
|
||||
onClose();
|
||||
|
@ -1718,7 +1725,13 @@ bool ContactsBox::editAdminFail(const RPCError &error) {
|
|||
if (mtpIsFlood(error)) return true;
|
||||
--_saveRequestId;
|
||||
_inner.chat()->invalidateParticipants();
|
||||
if (!_saveRequestId) onClose();
|
||||
if (!_saveRequestId) {
|
||||
if (error.type() == qstr("USER_RESTRICTED")) {
|
||||
Ui::showLayer(new InformBox(lang(lng_cant_do_this)));
|
||||
return true;
|
||||
}
|
||||
onClose();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1765,6 +1778,9 @@ bool ContactsBox::creationFail(const RPCError &error) {
|
|||
} else if (error.type() == "PEER_FLOOD") {
|
||||
Ui::showLayer(new InformBox(lng_cant_invite_not_contact(lt_more_info, textcmdLink(qsl("https://telegram.org/faq?_hash=can-39t-send-messages-to-non-contacts"), lang(lng_cant_more_info)))), KeepOtherLayers);
|
||||
return true;
|
||||
} else if (error.type() == qstr("USER_RESTRICTED")) {
|
||||
Ui::showLayer(new InformBox(lang(lng_cant_do_this)));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1909,7 +1925,7 @@ void MembersInner::paintDialog(Painter &p, PeerData *peer, MemberData *data, boo
|
|||
UserData *user = peer->asUser();
|
||||
|
||||
p.fillRect(0, 0, width(), _rowHeight, (sel ? st::contactsBgOver : st::white)->b);
|
||||
p.drawPixmapLeft(st::contactsPadding.left(), st::contactsPadding.top(), width(), peer->photo->pix(st::contactsPhotoSize));
|
||||
peer->paintUserpicLeft(p, st::contactsPhotoSize, st::contactsPadding.left(), st::contactsPadding.top(), width());
|
||||
|
||||
p.setPen(st::black);
|
||||
|
||||
|
@ -1994,7 +2010,7 @@ void MembersInner::loadProfilePhotos(int32 yFrom) {
|
|||
if (to > _rows.size()) to = _rows.size();
|
||||
|
||||
for (; from < to; ++from) {
|
||||
_rows[from]->photo->load();
|
||||
_rows[from]->loadUserpic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ void LanguageBox::mousePressEvent(QMouseEvent *e) {
|
|||
return;
|
||||
} else if (!loader.warnings().isEmpty()) {
|
||||
QString warn = loader.warnings();
|
||||
if (warn.size() > 256) warn = warn.mid(0, 254) + qsl("..");
|
||||
if (warn.size() > 256) warn = warn.mid(0, 253) + qsl("...");
|
||||
Ui::showLayer(new InformBox(qsl("Lang \"") + LanguageCodes[i] + qsl("\" warnings :(\n\nWarnings: ") + warn));
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -400,11 +400,11 @@ void PasscodeBox::onSave(bool force) {
|
|||
if (!_oldPasscode.isHidden()) {
|
||||
hashSha256(oldPasswordData.constData(), oldPasswordData.size(), oldPasswordHash.data());
|
||||
}
|
||||
int32 flags = MTPDaccount_passwordInputSettings::flag_new_salt | MTPDaccount_passwordInputSettings::flag_new_password_hash | MTPDaccount_passwordInputSettings::flag_hint;
|
||||
MTPDaccount_passwordInputSettings::Flags flags = MTPDaccount_passwordInputSettings::Flag::f_new_salt | MTPDaccount_passwordInputSettings::Flag::f_new_password_hash | MTPDaccount_passwordInputSettings::Flag::f_hint;
|
||||
if (_oldPasscode.isHidden() || _newPasscode.isHidden()) {
|
||||
flags |= MTPDaccount_passwordInputSettings::flag_email;
|
||||
flags |= MTPDaccount_passwordInputSettings::Flag::f_email;
|
||||
}
|
||||
MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_int(flags), MTP_string(_newSalt), MTP_string(newPasswordHash), MTP_string(hint), MTP_string(email)));
|
||||
MTPaccount_PasswordInputSettings settings(MTP_account_passwordInputSettings(MTP_flags(flags), MTP_string(_newSalt), MTP_string(newPasswordHash), MTP_string(hint), MTP_string(email)));
|
||||
_setRequest = MTP::send(MTPaccount_UpdatePasswordSettings(MTP_string(oldPasswordHash), settings), rpcDone(&PasscodeBox::setPasswordDone), rpcFail(&PasscodeBox::setPasswordFail));
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -69,8 +69,8 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW
|
|||
if (_animated) {
|
||||
int32 limitW = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
int32 limitH = st::confirmMaxHeight;
|
||||
maxW = dimensions.width();
|
||||
maxH = dimensions.height();
|
||||
maxW = qMax(dimensions.width(), 1);
|
||||
maxH = qMax(dimensions.height(), 1);
|
||||
if (maxW * limitH > maxH * limitW) {
|
||||
if (maxW < limitW) {
|
||||
maxH = maxH * limitW / maxW;
|
||||
|
@ -82,7 +82,7 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW
|
|||
maxH = limitH;
|
||||
}
|
||||
}
|
||||
_thumb = imagePix(_file->thumb.toImage(), maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), true, true, false, maxW, maxH);
|
||||
_thumb = imagePix(_file->thumb.toImage(), maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), ImagePixSmooth | ImagePixBlurred, maxW, maxH);
|
||||
} else {
|
||||
for (PreparedPhotoThumbs::const_iterator i = _file->photoThumbs.cbegin(), e = _file->photoThumbs.cend(); i != e; ++i) {
|
||||
if (i->width() >= maxW && i->height() >= maxH) {
|
||||
|
@ -124,7 +124,7 @@ PhotoSendBox::PhotoSendBox(const FileLoadResultPtr &file) : AbstractBox(st::boxW
|
|||
} else {
|
||||
_thumbw = st::msgFileThumbSize;
|
||||
}
|
||||
_thumb = imagePix(_thumb.toImage(), _thumbw * cIntRetinaFactor(), 0, true, false, true, st::msgFileThumbSize, st::msgFileThumbSize);
|
||||
_thumb = imagePix(_thumb.toImage(), _thumbw * cIntRetinaFactor(), 0, ImagePixSmooth | ImagePixRounded, st::msgFileThumbSize, st::msgFileThumbSize);
|
||||
}
|
||||
|
||||
_name.setText(st::semiboldFont, _file->filename, _textNameOptions);
|
||||
|
@ -274,7 +274,7 @@ void PhotoSendBox::paintEvent(QPaintEvent *e) {
|
|||
|
||||
p.drawSpriteCenter(inner, _isImage ? st::msgFileOutImage : st::msgFileOutFile);
|
||||
} else {
|
||||
p.drawPixmapLeft(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), width(), userDefPhoto(1)->pixRounded(st::msgFileSize));
|
||||
p.drawPixmapLeft(x + st::msgFilePadding.left(), y + st::msgFilePadding.top(), width(), userDefPhoto(1)->pixCircled(st::msgFileSize));
|
||||
}
|
||||
p.setFont(st::semiboldFont);
|
||||
p.setPen(st::black);
|
||||
|
@ -428,7 +428,7 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
|
|||
} else {
|
||||
_thumbw = st::msgFileThumbSize;
|
||||
}
|
||||
_thumb = imagePix(image->pix().toImage(), _thumbw * cIntRetinaFactor(), 0, true, false, true, st::msgFileThumbSize, st::msgFileThumbSize);
|
||||
_thumb = imagePix(image->pix().toImage(), _thumbw * cIntRetinaFactor(), 0, ImagePixSmooth | ImagePixRounded, st::msgFileThumbSize, st::msgFileThumbSize);
|
||||
}
|
||||
|
||||
if (doc) {
|
||||
|
@ -446,8 +446,8 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
|
|||
if (_animated) {
|
||||
int32 limitW = width() - st::boxPhotoPadding.left() - st::boxPhotoPadding.right();
|
||||
int32 limitH = st::confirmMaxHeight;
|
||||
maxW = dimensions.width();
|
||||
maxH = dimensions.height();
|
||||
maxW = qMax(dimensions.width(), 1);
|
||||
maxH = qMax(dimensions.height(), 1);
|
||||
if (maxW * limitH > maxH * limitW) {
|
||||
if (maxW < limitW) {
|
||||
maxH = maxH * limitW / maxW;
|
||||
|
@ -459,11 +459,11 @@ EditCaptionBox::EditCaptionBox(HistoryItem *msg) : AbstractBox(st::boxWideWidth)
|
|||
maxH = limitH;
|
||||
}
|
||||
}
|
||||
_thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), true, true, false, maxW, maxH);
|
||||
_thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), ImagePixSmooth | ImagePixBlurred, maxW, maxH);
|
||||
} else {
|
||||
maxW = dimensions.width();
|
||||
maxH = dimensions.height();
|
||||
_thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), true, false, false, maxW, maxH);
|
||||
_thumb = image->pixNoCache(maxW * cIntRetinaFactor(), maxH * cIntRetinaFactor(), ImagePixSmooth | ImagePixRounded, maxW, maxH);
|
||||
}
|
||||
int32 tw = _thumb.width(), th = _thumb.height();
|
||||
if (!tw || !th) {
|
||||
|
@ -648,15 +648,15 @@ void EditCaptionBox::onSave(bool ctrlShiftEnter) {
|
|||
return;
|
||||
}
|
||||
|
||||
int32 flags = 0;
|
||||
MTPchannels_EditMessage::Flags flags = 0;
|
||||
if (_previewCancelled) {
|
||||
flags |= MTPchannels_EditMessage::flag_no_webpage;
|
||||
flags |= MTPchannels_EditMessage::Flag::f_no_webpage;
|
||||
}
|
||||
MTPVector<MTPMessageEntity> sentEntities;
|
||||
if (!sentEntities.c_vector().v.isEmpty()) {
|
||||
flags |= MTPmessages_SendMessage::flag_entities;
|
||||
flags |= MTPchannels_EditMessage::Flag::f_entities;
|
||||
}
|
||||
_saveRequestId = MTP::send(MTPchannels_EditMessage(MTP_int(flags), item->history()->peer->asChannel()->inputChannel, MTP_int(item->id), MTP_string(_field->getLastText()), sentEntities), rpcDone(&EditCaptionBox::saveDone), rpcFail(&EditCaptionBox::saveFail));
|
||||
_saveRequestId = MTP::send(MTPchannels_EditMessage(MTP_flags(flags), item->history()->peer->asChannel()->inputChannel, MTP_int(item->id), MTP_string(_field->getLastText()), sentEntities), rpcDone(&EditCaptionBox::saveDone), rpcFail(&EditCaptionBox::saveFail));
|
||||
}
|
||||
|
||||
void EditCaptionBox::saveDone(const MTPUpdates &updates) {
|
||||
|
|
|
@ -117,17 +117,17 @@ bool StickerSetInner::failedSet(const RPCError &error) {
|
|||
}
|
||||
|
||||
void StickerSetInner::installDone(const MTPBool &result) {
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
|
||||
_setFlags &= ~MTPDstickerSet::flag_disabled;
|
||||
StickerSets::iterator it = sets.find(_setId);
|
||||
_setFlags &= ~MTPDstickerSet::Flag::f_disabled;
|
||||
auto it = sets.find(_setId);
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(_setId, StickerSet(_setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags));
|
||||
it = sets.insert(_setId, Stickers::Set(_setId, _setAccess, _setTitle, _setShortName, _setCount, _setHash, _setFlags));
|
||||
}
|
||||
it.value().stickers = _pack;
|
||||
it.value().emoji = _emoji;
|
||||
|
||||
StickerSetsOrder &order(cRefStickerSetsOrder());
|
||||
Stickers::Order &order(Global::RefStickerSetsOrder());
|
||||
int32 insertAtIndex = 0, currentIndex = order.indexOf(_setId);
|
||||
if (currentIndex != insertAtIndex) {
|
||||
if (currentIndex > 0) {
|
||||
|
@ -136,7 +136,7 @@ void StickerSetInner::installDone(const MTPBool &result) {
|
|||
order.insert(insertAtIndex, _setId);
|
||||
}
|
||||
|
||||
StickerSets::iterator custom = sets.find(CustomStickerSetId);
|
||||
auto custom = sets.find(Stickers::CustomSetId);
|
||||
if (custom != sets.cend()) {
|
||||
for (int32 i = 0, l = _pack.size(); i < l; ++i) {
|
||||
int32 removeIndex = custom->stickers.indexOf(_pack.at(i));
|
||||
|
@ -224,12 +224,8 @@ void StickerSetInner::paintEvent(QPaintEvent *e) {
|
|||
if (doc->status == FileReady) {
|
||||
doc->automaticLoad(0);
|
||||
}
|
||||
if (doc->sticker()->img->isNull() && doc->loaded() && doc->loaded(true)) {
|
||||
if (doc->data().isEmpty()) {
|
||||
doc->sticker()->img = ImagePtr(doc->already());
|
||||
} else {
|
||||
doc->sticker()->img = ImagePtr(doc->data());
|
||||
}
|
||||
if (doc->sticker()->img->isNull() && doc->loaded(DocumentData::FilePathResolveChecked)) {
|
||||
doc->sticker()->img = doc->data().isEmpty() ? ImagePtr(doc->filepath()) : ImagePtr(doc->data());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -260,8 +256,8 @@ bool StickerSetInner::loaded() const {
|
|||
|
||||
int32 StickerSetInner::notInstalled() const {
|
||||
if (!_loaded) return 0;
|
||||
StickerSets::const_iterator it = cStickerSets().constFind(_setId);
|
||||
if (it == cStickerSets().cend() || (it->flags & MTPDstickerSet::flag_disabled)) return _pack.size();
|
||||
auto it = Global::StickerSets().constFind(_setId);
|
||||
if (it == Global::StickerSets().cend() || (it->flags & MTPDstickerSet::Flag::f_disabled)) return _pack.size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -686,14 +682,14 @@ void StickersInner::rebuild() {
|
|||
int32 namew = st::boxWideWidth - namex - st::contactsPadding.right() - st::contactsCheckPosition.x() - qMax(qMax(_returnWidth, _removeWidth), _restoreWidth);
|
||||
|
||||
clear();
|
||||
const StickerSetsOrder &order(cStickerSetsOrder());
|
||||
const Stickers::Order &order(Global::StickerSetsOrder());
|
||||
_animStartTimes.reserve(order.size());
|
||||
|
||||
const StickerSets &sets(cStickerSets());
|
||||
for (int32 i = 0, l = order.size(); i < l; ++i) {
|
||||
StickerSets::const_iterator it = sets.constFind(order.at(i));
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
for (int i = 0, l = order.size(); i < l; ++i) {
|
||||
auto it = sets.constFind(order.at(i));
|
||||
if (it != sets.cend()) {
|
||||
bool disabled = (it->flags & MTPDstickerSet::flag_disabled);
|
||||
bool disabled = (it->flags & MTPDstickerSet::Flag::f_disabled);
|
||||
|
||||
DocumentData *sticker = it->stickers.isEmpty() ? 0 : it->stickers.at(0);
|
||||
int32 pixw = 0, pixh = 0;
|
||||
|
@ -718,10 +714,10 @@ void StickersInner::rebuild() {
|
|||
if (titleWidth > namew) {
|
||||
title = st::contactsNameFont->elided(title, namew);
|
||||
}
|
||||
bool official = (it->flags & MTPDstickerSet::flag_official);
|
||||
bool official = (it->flags & MTPDstickerSet::Flag::f_official);
|
||||
(disabled ? rowsDisabled : rows).push_back(new StickerSetRow(it->id, sticker, it->stickers.size(), title, official, disabled, pixw, pixh));
|
||||
_animStartTimes.push_back(0);
|
||||
if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_flag_NOT_LOADED)) {
|
||||
if (it->stickers.isEmpty() || (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded)) {
|
||||
App::api()->scheduleStickerSetRequest(it->id, it->access);
|
||||
}
|
||||
}
|
||||
|
@ -736,8 +732,8 @@ QVector<uint64> StickersInner::getOrder() const {
|
|||
result.reserve(_rows.size());
|
||||
for (int32 i = 0, l = _rows.size(); i < l; ++i) {
|
||||
if (_rows.at(i)->disabled) {
|
||||
StickerSets::const_iterator it = cStickerSets().constFind(_rows.at(i)->id);
|
||||
if (it == cStickerSets().cend() || !(it->flags & MTPDstickerSet::flag_official)) {
|
||||
auto it = Global::StickerSets().constFind(_rows.at(i)->id);
|
||||
if (it == Global::StickerSets().cend() || !(it->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -837,7 +833,7 @@ void StickersBox::reorderDone(const MTPBool &result) {
|
|||
bool StickersBox::reorderFail(const RPCError &result) {
|
||||
if (mtpIsFlood(result)) return false;
|
||||
_reorderRequest = 0;
|
||||
cSetLastStickersUpdate(0);
|
||||
Global::SetLastStickersUpdate(0);
|
||||
App::main()->updateStickers();
|
||||
onClose();
|
||||
return true;
|
||||
|
@ -861,12 +857,12 @@ void StickersBox::closePressed() {
|
|||
MTP::cancel(i.key());
|
||||
}
|
||||
_disenableRequests.clear();
|
||||
cSetLastStickersUpdate(0);
|
||||
Global::SetLastStickersUpdate(0);
|
||||
App::main()->updateStickers();
|
||||
} else if (_reorderRequest) {
|
||||
MTP::cancel(_reorderRequest);
|
||||
_reorderRequest = 0;
|
||||
cSetLastStickersUpdate(0);
|
||||
Global::SetLastStickersUpdate(0);
|
||||
App::main()->updateStickers();
|
||||
}
|
||||
}
|
||||
|
@ -918,11 +914,11 @@ void StickersBox::onSave() {
|
|||
|
||||
bool writeRecent = false;
|
||||
RecentStickerPack &recent(cGetRecentStickers());
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
|
||||
QVector<uint64> reorder = _inner.getOrder(), disabled = _inner.getDisabledSets();
|
||||
for (int32 i = 0, l = disabled.size(); i < l; ++i) {
|
||||
StickerSets::iterator it = sets.find(disabled.at(i));
|
||||
auto it = sets.find(disabled.at(i));
|
||||
if (it != sets.cend()) {
|
||||
for (RecentStickerPack::iterator i = recent.begin(); i != recent.cend();) {
|
||||
if (it->stickers.indexOf(i->first) >= 0) {
|
||||
|
@ -932,35 +928,35 @@ void StickersBox::onSave() {
|
|||
++i;
|
||||
}
|
||||
}
|
||||
if (!(it->flags & MTPDstickerSet::flag_disabled)) {
|
||||
if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) {
|
||||
MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
|
||||
if (it->flags & MTPDstickerSet::flag_official) {
|
||||
if (it->flags & MTPDstickerSet::Flag::f_official) {
|
||||
_disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolTrue()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType());
|
||||
it->flags |= MTPDstickerSet::flag_disabled;
|
||||
it->flags |= MTPDstickerSet::Flag::f_disabled;
|
||||
} else {
|
||||
_disenableRequests.insert(MTP::send(MTPmessages_UninstallStickerSet(setId), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType());
|
||||
int32 removeIndex = cStickerSetsOrder().indexOf(it->id);
|
||||
if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex);
|
||||
int removeIndex = Global::StickerSetsOrder().indexOf(it->id);
|
||||
if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex);
|
||||
sets.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
StickerSetsOrder &order(cRefStickerSetsOrder());
|
||||
Stickers::Order &order(Global::RefStickerSetsOrder());
|
||||
order.clear();
|
||||
for (int32 i = 0, l = reorder.size(); i < l; ++i) {
|
||||
StickerSets::iterator it = sets.find(reorder.at(i));
|
||||
for (int i = 0, l = reorder.size(); i < l; ++i) {
|
||||
auto it = sets.find(reorder.at(i));
|
||||
if (it != sets.cend()) {
|
||||
if ((it->flags & MTPDstickerSet::flag_disabled) && !disabled.contains(it->id)) {
|
||||
if ((it->flags & MTPDstickerSet::Flag::f_disabled) && !disabled.contains(it->id)) {
|
||||
MTPInputStickerSet setId = (it->id && it->access) ? MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access)) : MTP_inputStickerSetShortName(MTP_string(it->shortName));
|
||||
_disenableRequests.insert(MTP::send(MTPmessages_InstallStickerSet(setId, MTP_boolFalse()), rpcDone(&StickersBox::disenableDone), rpcFail(&StickersBox::disenableFail), 0, 5), NullType());
|
||||
it->flags &= ~MTPDstickerSet::flag_disabled;
|
||||
it->flags &= ~MTPDstickerSet::Flag::f_disabled;
|
||||
}
|
||||
order.push_back(reorder.at(i));
|
||||
}
|
||||
}
|
||||
for (StickerSets::iterator it = sets.begin(); it != sets.cend();) {
|
||||
if (it->id == CustomStickerSetId || it->id == RecentStickerSetId || order.contains(it->id)) {
|
||||
for (auto it = sets.begin(); it != sets.cend();) {
|
||||
if (it->id == Stickers::CustomSetId || it->id == Stickers::RecentSetId || order.contains(it->id)) {
|
||||
++it;
|
||||
} else {
|
||||
it = sets.erase(it);
|
||||
|
@ -996,12 +992,12 @@ void StickersBox::showAll() {
|
|||
|
||||
int32 stickerPacksCount(bool includeDisabledOfficial) {
|
||||
int32 result = 0;
|
||||
const StickerSetsOrder &order(cStickerSetsOrder());
|
||||
const StickerSets &sets(cStickerSets());
|
||||
for (int32 i = 0, l = order.size(); i < l; ++i) {
|
||||
StickerSets::const_iterator it = sets.constFind(order.at(i));
|
||||
const Stickers::Order &order(Global::StickerSetsOrder());
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
for (int i = 0, l = order.size(); i < l; ++i) {
|
||||
auto it = sets.constFind(order.at(i));
|
||||
if (it != sets.cend()) {
|
||||
if (!(it->flags & MTPDstickerSet::flag_disabled) || ((it->flags & MTPDstickerSet::flag_official) && includeDisabledOfficial)) {
|
||||
if (!(it->flags & MTPDstickerSet::Flag::f_disabled) || ((it->flags & MTPDstickerSet::Flag::f_official) && includeDisabledOfficial)) {
|
||||
++result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,7 +70,8 @@ private:
|
|||
bool _loaded;
|
||||
uint64 _setId, _setAccess;
|
||||
QString _title, _setTitle, _setShortName;
|
||||
int32 _setCount, _setHash, _setFlags;
|
||||
int32 _setCount, _setHash;
|
||||
MTPDstickerSet::Flags _setFlags;
|
||||
|
||||
int32 _bottom;
|
||||
MTPInputStickerSet _input;
|
||||
|
|
|
@ -160,7 +160,7 @@ void UsernameBox::onChanged() {
|
|||
}
|
||||
_checkTimer.stop();
|
||||
} else {
|
||||
int32 i, len = name.size();
|
||||
int32 len = name.size();
|
||||
for (int32 i = 0; i < len; ++i) {
|
||||
QChar ch = name.at(i);
|
||||
if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z') && (ch < '0' || ch > '9') && ch != '_' && (ch != '@' || i > 0)) {
|
||||
|
|
|
@ -20,10 +20,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
static const int32 AppVersion = 9033;
|
||||
static const wchar_t *AppVersionStr = L"0.9.33";
|
||||
static const bool DevVersion = false;
|
||||
//#define BETA_VERSION (9030002ULL) // just comment this line to build public version
|
||||
static const int32 AppVersion = 9036;
|
||||
static const wchar_t *AppVersionStr = L"0.9.36";
|
||||
static const bool DevVersion = true;
|
||||
//#define BETA_VERSION (9034004ULL) // just comment this line to build public version
|
||||
|
||||
static const wchar_t *AppNameOld = L"Telegram Win (Unofficial)";
|
||||
static const wchar_t *AppName = L"Telegram Desktop";
|
||||
|
@ -101,6 +101,9 @@ enum {
|
|||
MediaOverviewStartPerPage = 5,
|
||||
MediaOverviewPreloadCount = 4,
|
||||
|
||||
// a new message from the same sender is attached to previous within 15 minutes
|
||||
AttachMessageToPreviousSecondsDelta = 900,
|
||||
|
||||
AudioVoiceMsgSimultaneously = 4,
|
||||
AudioSongSimultaneously = 4,
|
||||
AudioCheckPositionTimeout = 100, // 100ms per check audio pos
|
||||
|
@ -197,7 +200,7 @@ inline const char *cGUIDStr() {
|
|||
return gGuidStr;
|
||||
}
|
||||
|
||||
inline const char **cPublicRSAKeys(uint32 &cnt) {
|
||||
inline const char **cPublicRSAKeys(int &keysCount) {
|
||||
static const char *(keys[]) = {"\
|
||||
-----BEGIN RSA PUBLIC KEY-----\n\
|
||||
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\n\
|
||||
|
@ -207,7 +210,7 @@ Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n\
|
|||
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\n\
|
||||
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n\
|
||||
-----END RSA PUBLIC KEY-----"};
|
||||
cnt = sizeof(keys) / sizeof(const char*);
|
||||
keysCount = arraysize(keys);
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
|
|
@ -247,11 +247,8 @@ void DialogsInner::peopleResultPaint(PeerData *peer, Painter &p, int32 w, bool a
|
|||
|
||||
History *history = App::history(peer->id);
|
||||
|
||||
if (peer->migrateTo()) {
|
||||
p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, peer->migrateTo()->photo->pix(st::dlgPhotoSize));
|
||||
} else {
|
||||
p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, peer->photo->pix(st::dlgPhotoSize));
|
||||
}
|
||||
PeerData *userpicPeer = (peer->migrateTo() ? peer->migrateTo() : peer);
|
||||
userpicPeer->paintUserpicLeft(p, st::dlgPhotoSize, st::dlgPaddingHor, st::dlgPaddingVer, fullWidth());
|
||||
|
||||
int32 nameleft = st::dlgPaddingHor + st::dlgPhotoSize + st::dlgPhotoPadding;
|
||||
int32 namewidth = w - nameleft - st::dlgPaddingHor;
|
||||
|
@ -299,7 +296,7 @@ void DialogsInner::searchInPeerPaint(Painter &p, int32 w, bool onlyBackground) c
|
|||
p.fillRect(fullRect, st::dlgBG->b);
|
||||
if (onlyBackground) return;
|
||||
|
||||
p.drawPixmap(st::dlgPaddingHor, st::dlgPaddingVer, _searchInPeer->photo->pix(st::dlgPhotoSize));
|
||||
_searchInPeer->paintUserpicLeft(p, st::dlgPhotoSize, st::dlgPaddingHor, st::dlgPaddingVer, fullWidth());
|
||||
|
||||
int32 nameleft = st::dlgPaddingHor + st::dlgPhotoSize + st::dlgPhotoPadding;
|
||||
int32 namewidth = w - nameleft - st::dlgPaddingHor * 2 - st::btnCancelSearch.width;
|
||||
|
@ -1373,7 +1370,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) {
|
|||
if (yFrom < otherStart) {
|
||||
dialogs.list.adjustCurrent(yFrom, st::dlgHeight);
|
||||
for (DialogRow *row = dialogs.list.current; row != dialogs.list.end && (row->pos * st::dlgHeight) < yTo; row = row->next) {
|
||||
row->history->peer->photo->load();
|
||||
row->history->peer->loadUserpic();
|
||||
}
|
||||
yFrom = 0;
|
||||
} else {
|
||||
|
@ -1383,7 +1380,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) {
|
|||
if (yTo > 0) {
|
||||
contactsNoDialogs.list.adjustCurrent(yFrom, st::dlgHeight);
|
||||
for (DialogRow *row = contactsNoDialogs.list.current; row != contactsNoDialogs.list.end && (row->pos * st::dlgHeight) < yTo; row = row->next) {
|
||||
row->history->peer->photo->load();
|
||||
row->history->peer->loadUserpic();
|
||||
}
|
||||
}
|
||||
} else if (_state == FilteredState || _state == SearchedState) {
|
||||
|
@ -1394,7 +1391,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) {
|
|||
if (to > _filterResults.size()) to = _filterResults.size();
|
||||
|
||||
for (; from < to; ++from) {
|
||||
_filterResults[from]->history->peer->photo->load();
|
||||
_filterResults[from]->history->peer->loadUserpic();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1405,7 +1402,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) {
|
|||
if (to > _peopleResults.size()) to = _peopleResults.size();
|
||||
|
||||
for (; from < to; ++from) {
|
||||
_peopleResults[from]->photo->load();
|
||||
_peopleResults[from]->loadUserpic();
|
||||
}
|
||||
}
|
||||
from = (yFrom > filteredOffset() + ((_peopleResults.isEmpty() ? 0 : st::searchedBarHeight) + st::searchedBarHeight) ? ((yFrom - filteredOffset() - (_peopleResults.isEmpty() ? 0 : st::searchedBarHeight) - st::searchedBarHeight) / int32(st::dlgHeight)) : 0) - _filterResults.size() - _peopleResults.size();
|
||||
|
@ -1415,7 +1412,7 @@ void DialogsInner::loadPeerPhotos(int32 yFrom) {
|
|||
if (to > _searchResults.size()) to = _searchResults.size();
|
||||
|
||||
for (; from < to; ++from) {
|
||||
_searchResults[from]->_item->history()->peer->photo->load();
|
||||
_searchResults[from]->_item->history()->peer->loadUserpic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2055,8 +2052,11 @@ bool DialogsWidget::onSearchMessages(bool searchCache) {
|
|||
MTP::cancel(_searchRequest);
|
||||
}
|
||||
if (_searchInPeer) {
|
||||
int32 flags = (_searchInPeer->isChannel() && !_searchInPeer->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0;
|
||||
_searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart));
|
||||
MTPmessages_Search::Flags flags = 0;
|
||||
if (_searchInPeer->isChannel() && !_searchInPeer->isMegagroup()) {
|
||||
flags |= MTPmessages_Search::Flag::f_important_only;
|
||||
}
|
||||
_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchPeerFromStart));
|
||||
} else {
|
||||
_searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(0), MTP_inputPeerEmpty(), MTP_int(0), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, DialogsSearchFromStart));
|
||||
}
|
||||
|
@ -2116,8 +2116,11 @@ void DialogsWidget::onSearchMore() {
|
|||
PeerData *offsetPeer = _inner.lastSearchPeer();
|
||||
MsgId offsetId = _inner.lastSearchId();
|
||||
if (_searchInPeer) {
|
||||
int32 flags = (_searchInPeer->isChannel() && !_searchInPeer->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0;
|
||||
_searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart));
|
||||
MTPmessages_Search::Flags flags = 0;
|
||||
if (_searchInPeer->isChannel() && !_searchInPeer->isMegagroup()) {
|
||||
flags |= MTPmessages_Search::Flag::f_important_only;
|
||||
}
|
||||
_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(flags), _searchInPeer->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchPeerFromOffset : DialogsSearchPeerFromStart));
|
||||
} else {
|
||||
_searchRequest = MTP::send(MTPmessages_SearchGlobal(MTP_string(_searchQuery), MTP_int(offsetDate), offsetPeer ? offsetPeer->input : MTP_inputPeerEmpty(), MTP_int(offsetId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart), rpcFail(&DialogsWidget::searchFailed, offsetId ? DialogsSearchFromOffset : DialogsSearchFromStart));
|
||||
}
|
||||
|
@ -2126,8 +2129,11 @@ void DialogsWidget::onSearchMore() {
|
|||
}
|
||||
} else if (_searchInMigrated && !_searchFullMigrated) {
|
||||
MsgId offsetMigratedId = _inner.lastSearchMigratedId();
|
||||
int32 flags = (_searchInMigrated->isChannel() && !_searchInMigrated->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0;
|
||||
_searchRequest = MTP::send(MTPmessages_Search(MTP_int(flags), _searchInMigrated->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart));
|
||||
MTPmessages_Search::Flags flags = 0;
|
||||
if (_searchInMigrated->isChannel() && !_searchInMigrated->isMegagroup()) {
|
||||
flags |= MTPmessages_Search::Flag::f_important_only;
|
||||
}
|
||||
_searchRequest = MTP::send(MTPmessages_Search(MTP_flags(flags), _searchInMigrated->input, MTP_string(_searchQuery), MTP_inputMessagesFilterEmpty(), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(offsetMigratedId), MTP_int(SearchPerPage)), rpcDone(&DialogsWidget::searchReceived, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart), rpcFail(&DialogsWidget::searchFailed, offsetMigratedId ? DialogsSearchMigratedFromOffset : DialogsSearchMigratedFromStart));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1097,7 +1097,7 @@ void EmojiPanInner::fillPanels(QVector<EmojiPanel*> &panels) {
|
|||
int y = 0;
|
||||
panels.reserve(emojiTabCount);
|
||||
for (int c = 0; c < emojiTabCount; ++c) {
|
||||
panels.push_back(new EmojiPanel(parentWidget(), lang(LangKey(lng_emoji_category0 + c)), NoneStickerSetId, true, y));
|
||||
panels.push_back(new EmojiPanel(parentWidget(), lang(LangKey(lng_emoji_category0 + c)), Stickers::NoneSetId, true, y));
|
||||
connect(panels.back(), SIGNAL(mousePressed()), this, SLOT(checkPickerHide()));
|
||||
int cnt = _counts[c], rows = (cnt / EmojiPanPerRow) + ((cnt % EmojiPanPerRow) ? 1 : 0);
|
||||
panels.back()->show();
|
||||
|
@ -1352,7 +1352,7 @@ void StickerPanInner::paintStickers(Painter &p, const QRect &r) {
|
|||
tilly = y + st::emojiPanHeader + (rows * st::stickerPanSize.height());
|
||||
if (r.top() >= tilly) continue;
|
||||
|
||||
bool special = (_sets[c].flags & MTPDstickerSet::flag_official);
|
||||
bool special = (_sets[c].flags & MTPDstickerSet::Flag::f_official);
|
||||
y += st::emojiPanHeader;
|
||||
|
||||
int32 fromrow = floorclamp(r.y() - y, st::stickerPanSize.height(), 0, rows);
|
||||
|
@ -1395,7 +1395,7 @@ void StickerPanInner::paintStickers(Painter &p, const QRect &r) {
|
|||
p.drawPixmapLeft(ppos, width(), sticker->sticker()->img->pix(w, h));
|
||||
}
|
||||
|
||||
if (hover > 0 && _sets[c].id == RecentStickerSetId && _custom.at(index)) {
|
||||
if (hover > 0 && _sets[c].id == Stickers::RecentSetId && _custom.at(index)) {
|
||||
float64 xHover = _sets[c].hovers[_sets[c].pack.size() + index];
|
||||
|
||||
QPoint xPos = pos + QPoint(st::stickerPanSize.width() - st::stickerPanDelete.pxWidth(), 0);
|
||||
|
@ -1509,7 +1509,7 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) {
|
|||
}
|
||||
|
||||
int tab = (_selected / MatrixRowShift), sel = _selected % MatrixRowShift;
|
||||
if (_sets[tab].id == RecentStickerSetId && sel >= _sets[tab].pack.size() && sel < _sets[tab].pack.size() * 2 && _custom.at(sel - _sets[tab].pack.size())) {
|
||||
if (_sets[tab].id == Stickers::RecentSetId && sel >= _sets[tab].pack.size() && sel < _sets[tab].pack.size() * 2 && _custom.at(sel - _sets[tab].pack.size())) {
|
||||
clearSelection(true);
|
||||
bool refresh = false;
|
||||
DocumentData *sticker = _sets[tab].pack.at(sel - _sets[tab].pack.size());
|
||||
|
@ -1522,8 +1522,8 @@ void StickerPanInner::mouseReleaseEvent(QMouseEvent *e) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
StickerSets::iterator it = sets.find(CustomStickerSetId);
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
auto it = sets.find(Stickers::CustomSetId);
|
||||
if (it != sets.cend()) {
|
||||
for (int32 i = 0, l = it->stickers.size(); i < l; ++i) {
|
||||
if (it->stickers.at(i) == sticker) {
|
||||
|
@ -1595,7 +1595,7 @@ void StickerPanInner::clearSelection(bool fast) {
|
|||
_animations.clear();
|
||||
if (_selected >= 0) {
|
||||
int index = qAbs(_selected), tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
|
||||
if (index >= 0 && tab < _sets.size() && _sets[tab].id == RecentStickerSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) {
|
||||
if (index >= 0 && tab < _sets.size() && _sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) {
|
||||
_sets[tab].hovers[sel] = 0;
|
||||
sel -= _sets[tab].pack.size();
|
||||
}
|
||||
|
@ -1603,7 +1603,7 @@ void StickerPanInner::clearSelection(bool fast) {
|
|||
}
|
||||
if (_pressedSel >= 0) {
|
||||
int index = qAbs(_pressedSel), tab = (index / MatrixRowShift), sel = index % MatrixRowShift;
|
||||
if (index >= 0 && tab < _sets.size() && _sets[tab].id == RecentStickerSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) {
|
||||
if (index >= 0 && tab < _sets.size() && _sets[tab].id == Stickers::RecentSetId && sel >= tab * MatrixRowShift + _sets[tab].pack.size()) {
|
||||
_sets[tab].hovers[sel] = 0;
|
||||
sel -= _sets[tab].pack.size();
|
||||
}
|
||||
|
@ -1640,11 +1640,11 @@ void StickerPanInner::hideFinish(bool completely) {
|
|||
void StickerPanInner::refreshStickers() {
|
||||
clearSelection(true);
|
||||
|
||||
const StickerSets &sets(cStickerSets());
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
_sets.clear(); _sets.reserve(sets.size() + 1);
|
||||
|
||||
refreshRecentStickers(false);
|
||||
for (StickerSetsOrder::const_iterator i = cStickerSetsOrder().cbegin(), e = cStickerSetsOrder().cend(); i != e; ++i) {
|
||||
for (auto i = Global::StickerSetsOrder().cbegin(), e = Global::StickerSetsOrder().cend(); i != e; ++i) {
|
||||
appendSet(*i);
|
||||
}
|
||||
|
||||
|
@ -1703,7 +1703,7 @@ void StickerPanInner::refreshSavedGifs() {
|
|||
if (_showingInlineItems) {
|
||||
const SavedGifs &saved(cSavedGifs());
|
||||
if (saved.isEmpty()) {
|
||||
showStickerSet(RecentStickerSetId);
|
||||
showStickerSet(Stickers::RecentSetId);
|
||||
return;
|
||||
} else {
|
||||
_inlineRows.reserve(saved.size());
|
||||
|
@ -1828,15 +1828,26 @@ StickerPanInner::InlineRow &StickerPanInner::layoutInlineRow(InlineRow &row, int
|
|||
int32 count = row.items.size();
|
||||
t_assert(count <= SavedGifsMaxPerRow);
|
||||
|
||||
// enumerate items in the order of growing maxWidth()
|
||||
// for that sort item indices by maxWidth()
|
||||
int indices[SavedGifsMaxPerRow];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
indices[i] = i;
|
||||
}
|
||||
std::sort(indices, indices + count, [&row](int a, int b) -> bool {
|
||||
return row.items.at(a)->maxWidth() < row.items.at(b)->maxWidth();
|
||||
});
|
||||
|
||||
row.height = 0;
|
||||
int32 availw = width() - st::inlineResultsLeft - st::inlineResultsSkip * (count - 1);
|
||||
for (int32 i = 0; i < count; ++i) {
|
||||
int32 w = sumWidth ? (row.items.at(i)->maxWidth() * availw / sumWidth) : row.items.at(i)->maxWidth();
|
||||
int32 actualw = qMax(w, int32(st::inlineResultsMinWidth));
|
||||
row.height = qMax(row.height, row.items.at(i)->resizeGetHeight(actualw));
|
||||
int availw = width() - st::inlineResultsLeft - st::inlineResultsSkip * (count - 1);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int index = indices[i];
|
||||
int w = sumWidth ? (row.items.at(index)->maxWidth() * availw / sumWidth) : row.items.at(index)->maxWidth();
|
||||
int actualw = qMax(w, int(st::inlineResultsMinWidth));
|
||||
row.height = qMax(row.height, row.items.at(index)->resizeGetHeight(actualw));
|
||||
if (sumWidth) {
|
||||
availw -= actualw;
|
||||
sumWidth -= row.items.at(i)->maxWidth();
|
||||
sumWidth -= row.items.at(index)->maxWidth();
|
||||
}
|
||||
}
|
||||
return row;
|
||||
|
@ -1871,7 +1882,7 @@ void StickerPanInner::preloadImages() {
|
|||
}
|
||||
|
||||
uint64 StickerPanInner::currentSet(int yOffset) const {
|
||||
if (_showingInlineItems) return NoneStickerSetId;
|
||||
if (_showingInlineItems) return Stickers::NoneSetId;
|
||||
|
||||
int y, ytill = 0;
|
||||
for (int i = 0, l = _sets.size(); i < l; ++i) {
|
||||
|
@ -1882,7 +1893,7 @@ uint64 StickerPanInner::currentSet(int yOffset) const {
|
|||
return _sets.at(i).id;
|
||||
}
|
||||
}
|
||||
return _sets.isEmpty() ? RecentStickerSetId : _sets.back().id;
|
||||
return _sets.isEmpty() ? Stickers::RecentSetId : _sets.back().id;
|
||||
}
|
||||
|
||||
void StickerPanInner::hideInlineRowsPanel() {
|
||||
|
@ -1894,7 +1905,7 @@ void StickerPanInner::hideInlineRowsPanel() {
|
|||
emit scrollToY(0);
|
||||
emit scrollUpdated();
|
||||
} else {
|
||||
showStickerSet(RecentStickerSetId);
|
||||
showStickerSet(Stickers::RecentSetId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2039,9 +2050,9 @@ bool StickerPanInner::ui_isInlineItemBeingChosen() {
|
|||
}
|
||||
|
||||
void StickerPanInner::appendSet(uint64 setId) {
|
||||
const StickerSets &sets(cStickerSets());
|
||||
StickerSets::const_iterator it = sets.constFind(setId);
|
||||
if (it == sets.cend() || (it->flags & MTPDstickerSet::flag_disabled) || it->stickers.isEmpty()) return;
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
auto it = sets.constFind(setId);
|
||||
if (it == sets.cend() || (it->flags & MTPDstickerSet::Flag::f_disabled) || it->stickers.isEmpty()) return;
|
||||
|
||||
StickerPack pack;
|
||||
pack.reserve(it->stickers.size());
|
||||
|
@ -2064,14 +2075,14 @@ void StickerPanInner::refreshRecent() {
|
|||
void StickerPanInner::refreshRecentStickers(bool performResize) {
|
||||
_custom.clear();
|
||||
clearSelection(true);
|
||||
StickerSets::const_iterator customIt = cStickerSets().constFind(CustomStickerSetId);
|
||||
if (cGetRecentStickers().isEmpty() && (customIt == cStickerSets().cend() || customIt->stickers.isEmpty())) {
|
||||
if (!_sets.isEmpty() && _sets.at(0).id == RecentStickerSetId) {
|
||||
auto customIt = Global::StickerSets().constFind(Stickers::CustomSetId);
|
||||
if (cGetRecentStickers().isEmpty() && (customIt == Global::StickerSets().cend() || customIt->stickers.isEmpty())) {
|
||||
if (!_sets.isEmpty() && _sets.at(0).id == Stickers::RecentSetId) {
|
||||
_sets.pop_front();
|
||||
}
|
||||
} else {
|
||||
StickerPack recent;
|
||||
int32 customCnt = (customIt == cStickerSets().cend() ? 0 : customIt->stickers.size());
|
||||
int32 customCnt = (customIt == Global::StickerSets().cend() ? 0 : customIt->stickers.size());
|
||||
QMap<DocumentData*, bool> recentOnly;
|
||||
recent.reserve(cGetRecentStickers().size() + customCnt);
|
||||
_custom.reserve(cGetRecentStickers().size() + customCnt);
|
||||
|
@ -2090,8 +2101,8 @@ void StickerPanInner::refreshRecentStickers(bool performResize) {
|
|||
_custom.push_back(true);
|
||||
}
|
||||
}
|
||||
if (_sets.isEmpty() || _sets.at(0).id != RecentStickerSetId) {
|
||||
_sets.push_back(DisplayedSet(RecentStickerSetId, MTPDstickerSet::flag_official, lang(lng_emoji_category0), recent.size() * 2, recent));
|
||||
if (_sets.isEmpty() || _sets.at(0).id != Stickers::RecentSetId) {
|
||||
_sets.push_back(DisplayedSet(Stickers::RecentSetId, MTPDstickerSet::Flag::f_official, lang(lng_emoji_category0), recent.size() * 2, recent));
|
||||
} else {
|
||||
_sets[0].pack = recent;
|
||||
_sets[0].hovers.resize(recent.size() * 2);
|
||||
|
@ -2112,12 +2123,12 @@ void StickerPanInner::refreshRecentStickers(bool performResize) {
|
|||
void StickerPanInner::fillIcons(QList<StickerIcon> &icons) {
|
||||
icons.clear();
|
||||
icons.reserve(_sets.size() + 1);
|
||||
if (!cSavedGifs().isEmpty()) icons.push_back(StickerIcon(NoneStickerSetId));
|
||||
if (!cSavedGifs().isEmpty()) icons.push_back(StickerIcon(Stickers::NoneSetId));
|
||||
|
||||
if (_sets.isEmpty()) return;
|
||||
int32 i = 0;
|
||||
if (_sets.at(0).id == RecentStickerSetId) ++i;
|
||||
if (i > 0) icons.push_back(StickerIcon(RecentStickerSetId));
|
||||
if (_sets.at(0).id == Stickers::RecentSetId) ++i;
|
||||
if (i > 0) icons.push_back(StickerIcon(Stickers::RecentSetId));
|
||||
for (int32 l = _sets.size(); i < l; ++i) {
|
||||
DocumentData *s = _sets.at(i).pack.at(0);
|
||||
int32 availw = st::rbEmoji.width - 2 * st::stickerIconPadding, availh = st::rbEmoji.height - 2 * st::stickerIconPadding;
|
||||
|
@ -2143,7 +2154,7 @@ void StickerPanInner::fillPanels(QVector<EmojiPanel*> &panels) {
|
|||
panels.clear();
|
||||
|
||||
if (_showingInlineItems) {
|
||||
panels.push_back(new EmojiPanel(parentWidget(), _showingSavedGifs ? lang(lng_saved_gifs) : _inlineBotTitle, NoneStickerSetId, true, 0));
|
||||
panels.push_back(new EmojiPanel(parentWidget(), _showingSavedGifs ? lang(lng_saved_gifs) : _inlineBotTitle, Stickers::NoneSetId, true, 0));
|
||||
panels.back()->show();
|
||||
return;
|
||||
}
|
||||
|
@ -2153,7 +2164,7 @@ void StickerPanInner::fillPanels(QVector<EmojiPanel*> &panels) {
|
|||
int y = 0;
|
||||
panels.reserve(_sets.size());
|
||||
for (int32 i = 0, l = _sets.size(); i < l; ++i) {
|
||||
bool special = (_sets.at(i).flags & MTPDstickerSet::flag_official);
|
||||
bool special = (_sets.at(i).flags & MTPDstickerSet::Flag::f_official);
|
||||
panels.push_back(new EmojiPanel(parentWidget(), _sets.at(i).title, _sets.at(i).id, special, y));
|
||||
panels.back()->show();
|
||||
connect(panels.back(), SIGNAL(deleteClicked(quint64)), this, SIGNAL(removing(quint64)));
|
||||
|
@ -2260,7 +2271,7 @@ void StickerPanInner::updateSelected() {
|
|||
for (int c = 0, l = _sets.size(); c < l; ++c) {
|
||||
const DisplayedSet &set(_sets.at(c));
|
||||
int cnt = set.pack.size();
|
||||
bool special = (set.flags & MTPDstickerSet::flag_official);
|
||||
bool special = (set.flags & MTPDstickerSet::Flag::f_official);
|
||||
|
||||
y = ytill;
|
||||
ytill = y + st::emojiPanHeader + ((cnt / StickerPanPerRow) + ((cnt % StickerPanPerRow) ? 1 : 0)) * st::stickerPanSize.height();
|
||||
|
@ -2271,7 +2282,7 @@ void StickerPanInner::updateSelected() {
|
|||
if (selIndex >= set.pack.size()) {
|
||||
selIndex = -1;
|
||||
} else {
|
||||
if (set.id == RecentStickerSetId && _custom[selIndex]) {
|
||||
if (set.id == Stickers::RecentSetId && _custom[selIndex]) {
|
||||
int32 inx = sx - (selIndex % StickerPanPerRow) * st::stickerPanSize.width(), iny = p.y() - y - ((selIndex / StickerPanPerRow) * st::stickerPanSize.height());
|
||||
if (inx >= st::stickerPanSize.width() - st::stickerPanDelete.pxWidth() && iny < st::stickerPanDelete.pxHeight()) {
|
||||
selIndex += set.pack.size();
|
||||
|
@ -2286,11 +2297,11 @@ void StickerPanInner::updateSelected() {
|
|||
|
||||
bool startanim = false;
|
||||
int oldSel = _selected, oldSelTab = oldSel / MatrixRowShift, xOldSel = -1, newSel = selIndex, newSelTab = newSel / MatrixRowShift, xNewSel = -1;
|
||||
if (oldSel >= 0 && oldSelTab < _sets.size() && _sets[oldSelTab].id == RecentStickerSetId && oldSel >= oldSelTab * MatrixRowShift + _sets[oldSelTab].pack.size()) {
|
||||
if (oldSel >= 0 && oldSelTab < _sets.size() && _sets[oldSelTab].id == Stickers::RecentSetId && oldSel >= oldSelTab * MatrixRowShift + _sets[oldSelTab].pack.size()) {
|
||||
xOldSel = oldSel;
|
||||
oldSel -= _sets[oldSelTab].pack.size();
|
||||
}
|
||||
if (newSel >= 0 && newSelTab < _sets.size() && _sets[newSelTab].id == RecentStickerSetId && newSel >= newSelTab * MatrixRowShift + _sets[newSelTab].pack.size()) {
|
||||
if (newSel >= 0 && newSelTab < _sets.size() && _sets[newSelTab].id == Stickers::RecentSetId && newSel >= newSelTab * MatrixRowShift + _sets[newSelTab].pack.size()) {
|
||||
xNewSel = newSel;
|
||||
newSel -= _sets[newSelTab].pack.size();
|
||||
}
|
||||
|
@ -2390,7 +2401,7 @@ void StickerPanInner::step_selected(uint64 ms, bool timer) {
|
|||
void StickerPanInner::showStickerSet(uint64 setId) {
|
||||
clearSelection(true);
|
||||
|
||||
if (setId == NoneStickerSetId) {
|
||||
if (setId == Stickers::NoneSetId) {
|
||||
bool wasNotShowingGifs = !_showingInlineItems;
|
||||
if (wasNotShowingGifs) {
|
||||
_showingInlineItems = true;
|
||||
|
@ -2460,7 +2471,7 @@ EmojiPanel::EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool
|
|||
, _setId(setId)
|
||||
, _special(special)
|
||||
, _deleteVisible(false)
|
||||
, _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // NoneStickerSetId if in emoji
|
||||
, _delete(special ? 0 : new IconedButton(this, st::notifyClose)) { // Stickers::NoneSetId if in emoji
|
||||
resize(st::emojiPanWidth, st::emojiPanHeader);
|
||||
setMouseTracking(true);
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
|
@ -2484,11 +2495,11 @@ void EmojiPanel::setText(const QString &text) {
|
|||
void EmojiPanel::updateText() {
|
||||
int32 availw = st::emojiPanWidth - st::emojiPanHeaderLeft * 2;
|
||||
if (_deleteVisible) {
|
||||
if (!_special && _setId != NoneStickerSetId) {
|
||||
if (!_special && _setId != Stickers::NoneSetId) {
|
||||
availw -= st::notifyClose.icon.pxWidth() + st::emojiPanHeaderLeft;
|
||||
}
|
||||
} else {
|
||||
QString switchText = lang((_setId != NoneStickerSetId) ? lng_switch_emoji : (cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs));
|
||||
QString switchText = lang((_setId != Stickers::NoneSetId) ? lng_switch_emoji : (cSavedGifs().isEmpty() ? lng_switch_stickers : lng_switch_stickers_gifs));
|
||||
availw -= st::emojiSwitchSkip + st::emojiPanHeaderFont->width(switchText);
|
||||
}
|
||||
_text = st::emojiPanHeaderFont->elided(_fullText, availw);
|
||||
|
@ -2781,7 +2792,7 @@ void EmojiPan::paintEvent(QPaintEvent *e) {
|
|||
if (!_icons.isEmpty()) {
|
||||
int32 x = _iconsLeft, i = 0, selxrel = _iconsLeft + _iconSelX.current(), selx = selxrel - _iconsX.current();
|
||||
for (int32 l = _icons.size(); i < l && !_icons.at(i).sticker; ++i) {
|
||||
bool gifs = (_icons.at(i).setId == NoneStickerSetId);
|
||||
bool gifs = (_icons.at(i).setId == Stickers::NoneSetId);
|
||||
if (selxrel != x) {
|
||||
p.drawSpriteLeft(x + st::rbEmojiRecent.imagePos.x(), _iconsTop + st::rbEmojiRecent.imagePos.y(), width(), gifs ? st::savedGifsOver : st::rbEmojiRecent.imageRect);
|
||||
}
|
||||
|
@ -3512,9 +3523,9 @@ void EmojiPan::onSwitch() {
|
|||
Notify::clipStopperHidden(ClipStopperSavedGifsPanel);
|
||||
} else {
|
||||
if (cShowingSavedGifs() && cSavedGifs().isEmpty()) {
|
||||
s_inner.showStickerSet(DefaultStickerSetId);
|
||||
} else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && cStickerSets().isEmpty()) {
|
||||
s_inner.showStickerSet(NoneStickerSetId);
|
||||
s_inner.showStickerSet(Stickers::DefaultSetId);
|
||||
} else if (!cShowingSavedGifs() && !cSavedGifs().isEmpty() && Global::StickerSets().isEmpty()) {
|
||||
s_inner.showStickerSet(Stickers::NoneSetId);
|
||||
} else {
|
||||
s_inner.updateShowingSavedGifs();
|
||||
}
|
||||
|
@ -3552,8 +3563,8 @@ void EmojiPan::onSwitch() {
|
|||
}
|
||||
|
||||
void EmojiPan::onRemoveSet(quint64 setId) {
|
||||
StickerSets::const_iterator it = cStickerSets().constFind(setId);
|
||||
if (it != cStickerSets().cend() && !(it->flags & MTPDstickerSet::flag_official)) {
|
||||
auto it = Global::StickerSets().constFind(setId);
|
||||
if (it != Global::StickerSets().cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
_removingSetId = it->id;
|
||||
ConfirmBox *box = new ConfirmBox(lng_stickers_remove_pack(lt_sticker_pack, it->title), lang(lng_box_remove));
|
||||
connect(box, SIGNAL(confirmed()), this, SLOT(onRemoveSetSure()));
|
||||
|
@ -3564,8 +3575,8 @@ void EmojiPan::onRemoveSet(quint64 setId) {
|
|||
|
||||
void EmojiPan::onRemoveSetSure() {
|
||||
Ui::hideLayer();
|
||||
StickerSets::iterator it = cRefStickerSets().find(_removingSetId);
|
||||
if (it != cRefStickerSets().cend() && !(it->flags & MTPDstickerSet::flag_official)) {
|
||||
auto it = Global::RefStickerSets().find(_removingSetId);
|
||||
if (it != Global::RefStickerSets().cend() && !(it->flags & MTPDstickerSet::Flag::f_official)) {
|
||||
if (it->id && it->access) {
|
||||
MTP::send(MTPmessages_UninstallStickerSet(MTP_inputStickerSetID(MTP_long(it->id), MTP_long(it->access))));
|
||||
} else if (!it->shortName.isEmpty()) {
|
||||
|
@ -3581,9 +3592,9 @@ void EmojiPan::onRemoveSetSure() {
|
|||
++i;
|
||||
}
|
||||
}
|
||||
cRefStickerSets().erase(it);
|
||||
int32 removeIndex = cStickerSetsOrder().indexOf(_removingSetId);
|
||||
if (removeIndex >= 0) cRefStickerSetsOrder().removeAt(removeIndex);
|
||||
Global::RefStickerSets().erase(it);
|
||||
int removeIndex = Global::StickerSetsOrder().indexOf(_removingSetId);
|
||||
if (removeIndex >= 0) Global::RefStickerSetsOrder().removeAt(removeIndex);
|
||||
refreshStickers();
|
||||
Local::writeStickers();
|
||||
if (writeRecent) Local::writeUserSettings();
|
||||
|
@ -3944,8 +3955,8 @@ void MentionsInner::paintEvent(QPaintEvent *e) {
|
|||
second = st::mentionFont->elided(second, unamewidth - firstwidth);
|
||||
}
|
||||
}
|
||||
user->photo->load();
|
||||
p.drawPixmap(st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), user->photo->pixRounded(st::mentionPhotoSize));
|
||||
user->loadUserpic();
|
||||
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
|
||||
user->nameText.drawElided(p, 2 * st::mentionPadding.left() + st::mentionPhotoSize, i * st::mentionHeight + st::mentionTop, namewidth);
|
||||
|
||||
p.setFont(st::mentionFont->f);
|
||||
|
@ -3986,10 +3997,8 @@ void MentionsInner::paintEvent(QPaintEvent *e) {
|
|||
if (hasUsername || botStatus == 0 || botStatus == 2) {
|
||||
toHighlight += '@' + user->username;
|
||||
}
|
||||
if (true || _parent->chat() || botStatus == 0 || botStatus == 2) {
|
||||
user->photo->load();
|
||||
p.drawPixmap(st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), user->photo->pixRounded(st::mentionPhotoSize));
|
||||
}
|
||||
user->loadUserpic();
|
||||
user->paintUserpicLeft(p, st::mentionPhotoSize, st::mentionPadding.left(), i * st::mentionHeight + st::mentionPadding.top(), width());
|
||||
|
||||
int32 addleft = 0, widthleft = mentionwidth;
|
||||
QString first = (_parent->filter().size() < 2) ? QString() : ('/' + toHighlight.mid(0, _parent->filter().size() - 1)), second = (_parent->filter().size() < 2) ? ('/' + toHighlight) : toHighlight.mid(_parent->filter().size() - 1);
|
||||
|
@ -4353,6 +4362,18 @@ bool MentionsDropdown::clearFilteredBotCommands() {
|
|||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
template <typename T, typename U>
|
||||
inline int indexOfInFirstN(const T &v, const U &elem, int last) {
|
||||
for (auto b = v.cbegin(), i = b, e = b + qMax(v.size(), last); i != e; ++i) {
|
||||
if (*i == elem) {
|
||||
return (i - b);
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
void MentionsDropdown::updateFiltered(bool resetScroll) {
|
||||
int32 now = unixtime(), recentInlineBots = 0;
|
||||
MentionRows mrows;
|
||||
|
@ -4361,15 +4382,15 @@ void MentionsDropdown::updateFiltered(bool resetScroll) {
|
|||
StickerPack srows;
|
||||
if (_emoji) {
|
||||
QMap<uint64, uint64> setsToRequest;
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
const StickerSetsOrder &order(cStickerSetsOrder());
|
||||
for (int32 i = 0, l = order.size(); i < l; ++i) {
|
||||
StickerSets::iterator it = sets.find(order.at(i));
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
const Stickers::Order &order(Global::StickerSetsOrder());
|
||||
for (int i = 0, l = order.size(); i < l; ++i) {
|
||||
auto it = sets.find(order.at(i));
|
||||
if (it != sets.cend()) {
|
||||
if (it->emoji.isEmpty()) {
|
||||
setsToRequest.insert(it->id, it->access);
|
||||
it->flags |= MTPDstickerSet_flag_NOT_LOADED;
|
||||
} else if (!(it->flags & MTPDstickerSet::flag_disabled)) {
|
||||
it->flags |= MTPDstickerSet_ClientFlag::f_not_loaded;
|
||||
} else if (!(it->flags & MTPDstickerSet::Flag::f_disabled)) {
|
||||
StickersByEmojiMap::const_iterator i = it->emoji.constFind(emojiGetNoColor(_emoji));
|
||||
if (i != it->emoji.cend()) {
|
||||
srows += *i;
|
||||
|
@ -4413,6 +4434,7 @@ void MentionsDropdown::updateFiltered(bool resetScroll) {
|
|||
UserData *user = i.key();
|
||||
if (user->username.isEmpty()) continue;
|
||||
if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
ordered.insertMulti(App::onlineForSort(user, now), user);
|
||||
}
|
||||
}
|
||||
|
@ -4420,6 +4442,7 @@ void MentionsDropdown::updateFiltered(bool resetScroll) {
|
|||
UserData *user = *i;
|
||||
if (user->username.isEmpty()) continue;
|
||||
if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
mrows.push_back(user);
|
||||
if (!ordered.isEmpty()) {
|
||||
ordered.remove(App::onlineForSort(user, now), user);
|
||||
|
@ -4441,6 +4464,7 @@ void MentionsDropdown::updateFiltered(bool resetScroll) {
|
|||
UserData *user = *i;
|
||||
if (user->username.isEmpty()) continue;
|
||||
if (_filter.size() > 1 && (!user->username.startsWith(_filter.midRef(1), Qt::CaseInsensitive) || user->username.size() + 1 == _filter.size())) continue;
|
||||
if (indexOfInFirstN(mrows, user, recentInlineBots) >= 0) continue;
|
||||
mrows.push_back(user);
|
||||
}
|
||||
}
|
||||
|
@ -4477,8 +4501,7 @@ void MentionsDropdown::updateFiltered(bool resetScroll) {
|
|||
if (_channel->mgInfo->bots.isEmpty()) {
|
||||
if (!_channel->mgInfo->botStatus && App::api()) App::api()->requestBots(_channel);
|
||||
} else {
|
||||
for (MegagroupInfo::Bots::const_iterator i = _channel->mgInfo->bots.cbegin(), e = _channel->mgInfo->bots.cend(); i != e; ++i) {
|
||||
UserData *user = i.key();
|
||||
for_const (auto *user, _channel->mgInfo->bots) {
|
||||
if (!user->botInfo) continue;
|
||||
if (!user->botInfo->inited && App::api()) App::api()->requestFullPeer(user);
|
||||
if (user->botInfo->commands.isEmpty()) continue;
|
||||
|
@ -4546,6 +4569,7 @@ void MentionsDropdown::rowsUpdated(const MentionRows &mrows, const HashtagRows &
|
|||
_scroll.show();
|
||||
}
|
||||
recount(resetScroll);
|
||||
update();
|
||||
if (hidden) {
|
||||
hide();
|
||||
showStart();
|
||||
|
|
|
@ -420,10 +420,10 @@ private:
|
|||
int32 _top;
|
||||
|
||||
struct DisplayedSet {
|
||||
DisplayedSet(uint64 id, int32 flags, const QString &title, int32 hoversSize, const StickerPack &pack = StickerPack()) : id(id), flags(flags), title(title), hovers(hoversSize, 0), pack(pack) {
|
||||
DisplayedSet(uint64 id, MTPDstickerSet::Flags flags, const QString &title, int32 hoversSize, const StickerPack &pack = StickerPack()) : id(id), flags(flags), title(title), hovers(hoversSize, 0), pack(pack) {
|
||||
}
|
||||
uint64 id;
|
||||
int32 flags;
|
||||
MTPDstickerSet::Flags flags;
|
||||
QString title;
|
||||
QVector<float64> hovers;
|
||||
StickerPack pack;
|
||||
|
@ -482,7 +482,7 @@ class EmojiPanel : public TWidget {
|
|||
|
||||
public:
|
||||
|
||||
EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY); // NoneStickerSetId if in emoji
|
||||
EmojiPanel(QWidget *parent, const QString &text, uint64 setId, bool special, int32 wantedY); // Stickers::NoneSetId if in emoji
|
||||
void setText(const QString &text);
|
||||
void setDeleteVisible(bool isVisible);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "window.h"
|
||||
#include "mainwidget.h"
|
||||
#include "application.h"
|
||||
|
||||
#include "layerwidget.h"
|
||||
#include "lang.h"
|
||||
|
@ -143,6 +144,12 @@ namespace Ui {
|
|||
return false;
|
||||
}
|
||||
|
||||
void autoplayMediaInlineAsync(const FullMsgId &msgId) {
|
||||
if (MainWidget *m = App::main()) {
|
||||
QMetaObject::invokeMethod(m, "ui_autoplayMediaInlineAsync", Qt::QueuedConnection, Q_ARG(qint32, msgId.channel), Q_ARG(qint32, msgId.msg));
|
||||
}
|
||||
}
|
||||
|
||||
void showPeerHistory(const PeerId &peer, MsgId msgId, bool back) {
|
||||
if (MainWidget *m = App::main()) m->ui_showPeerHistory(peer, msgId, back);
|
||||
}
|
||||
|
@ -197,10 +204,6 @@ namespace Notify {
|
|||
if (MainWidget *m = App::main()) m->notify_clipStopperHidden(type);
|
||||
}
|
||||
|
||||
void historyItemResized(const HistoryItem *item, bool scrollToIt) {
|
||||
if (MainWidget *m = App::main()) m->notify_historyItemResized(item, scrollToIt);
|
||||
}
|
||||
|
||||
void historyItemLayoutChanged(const HistoryItem *item) {
|
||||
if (MainWidget *m = App::main()) m->notify_historyItemLayoutChanged(item);
|
||||
}
|
||||
|
@ -209,6 +212,16 @@ namespace Notify {
|
|||
if (MainWidget *m = App::main()) m->notify_automaticLoadSettingsChangedGif();
|
||||
}
|
||||
|
||||
void handlePendingHistoryUpdate() {
|
||||
if (MainWidget *m = App::main()) {
|
||||
m->notify_handlePendingHistoryUpdate();
|
||||
}
|
||||
for_const (HistoryItem *item, Global::PendingRepaintItems()) {
|
||||
Ui::repaintHistoryItem(item);
|
||||
}
|
||||
Global::RefPendingRepaintItems().clear();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#define DefineReadOnlyVar(Namespace, Type, Name) const Type &Name() { \
|
||||
|
@ -226,14 +239,22 @@ void Set##Name(const Type &Name) { \
|
|||
Namespace##Data->Name = Name; \
|
||||
}
|
||||
|
||||
struct SandboxDataStruct {
|
||||
QString LangSystemISO;
|
||||
int32 LangSystem = languageDefault;
|
||||
namespace Sandbox {
|
||||
|
||||
QByteArray LastCrashDump;
|
||||
ConnectionProxy PreLaunchProxy;
|
||||
};
|
||||
SandboxDataStruct *SandboxData = 0;
|
||||
namespace internal {
|
||||
|
||||
struct Data {
|
||||
QString LangSystemISO;
|
||||
int32 LangSystem = languageDefault;
|
||||
|
||||
QByteArray LastCrashDump;
|
||||
ConnectionProxy PreLaunchProxy;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Sandbox::internal::Data *SandboxData = 0;
|
||||
uint64 SandboxUserTag = 0;
|
||||
|
||||
namespace Sandbox {
|
||||
|
@ -320,7 +341,7 @@ namespace Sandbox {
|
|||
}
|
||||
|
||||
void start() {
|
||||
SandboxData = new SandboxDataStruct();
|
||||
SandboxData = new internal::Data();
|
||||
|
||||
SandboxData->LangSystemISO = psCurrentLanguage();
|
||||
if (SandboxData->LangSystemISO.isEmpty()) SandboxData->LangSystemISO = qstr("en");
|
||||
|
@ -349,32 +370,52 @@ namespace Sandbox {
|
|||
|
||||
}
|
||||
|
||||
struct GlobalDataStruct {
|
||||
uint64 LaunchId = 0;
|
||||
namespace Global {
|
||||
namespace internal {
|
||||
|
||||
Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout;
|
||||
bool AdaptiveForWide = true;
|
||||
struct Data {
|
||||
uint64 LaunchId = 0;
|
||||
SingleDelayedCall HandleHistoryUpdate = { App::app(), "call_handleHistoryUpdate" };
|
||||
|
||||
// config
|
||||
int32 ChatSizeMax = 200;
|
||||
int32 MegagroupSizeMax = 1000;
|
||||
int32 ForwardedCountMax = 100;
|
||||
int32 OnlineUpdatePeriod = 120000;
|
||||
int32 OfflineBlurTimeout = 5000;
|
||||
int32 OfflineIdleTimeout = 30000;
|
||||
int32 OnlineFocusTimeout = 1000;
|
||||
int32 OnlineCloudTimeout = 300000;
|
||||
int32 NotifyCloudDelay = 30000;
|
||||
int32 NotifyDefaultDelay = 1500;
|
||||
int32 ChatBigSize = 10;
|
||||
int32 PushChatPeriod = 60000;
|
||||
int32 PushChatLimit = 2;
|
||||
int32 SavedGifsLimit = 200;
|
||||
int32 EditTimeLimit = 172800;
|
||||
Adaptive::Layout AdaptiveLayout = Adaptive::NormalLayout;
|
||||
bool AdaptiveForWide = true;
|
||||
|
||||
Global::HiddenPinnedMessagesMap HiddenPinnedMessages;
|
||||
};
|
||||
GlobalDataStruct *GlobalData = 0;
|
||||
int32 DebugLoggingFlags = 0;
|
||||
|
||||
// config
|
||||
int32 ChatSizeMax = 200;
|
||||
int32 MegagroupSizeMax = 1000;
|
||||
int32 ForwardedCountMax = 100;
|
||||
int32 OnlineUpdatePeriod = 120000;
|
||||
int32 OfflineBlurTimeout = 5000;
|
||||
int32 OfflineIdleTimeout = 30000;
|
||||
int32 OnlineFocusTimeout = 1000;
|
||||
int32 OnlineCloudTimeout = 300000;
|
||||
int32 NotifyCloudDelay = 30000;
|
||||
int32 NotifyDefaultDelay = 1500;
|
||||
int32 ChatBigSize = 10;
|
||||
int32 PushChatPeriod = 60000;
|
||||
int32 PushChatLimit = 2;
|
||||
int32 SavedGifsLimit = 200;
|
||||
int32 EditTimeLimit = 172800;
|
||||
|
||||
HiddenPinnedMessagesMap HiddenPinnedMessages;
|
||||
|
||||
PendingItemsMap PendingRepaintItems;
|
||||
|
||||
Stickers::Sets StickerSets;
|
||||
Stickers::Order StickerSetsOrder;
|
||||
uint64 LastStickersUpdate = 0;
|
||||
|
||||
MTP::DcOptions DcOptions;
|
||||
|
||||
CircleMasksMap CircleMasks;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Global::internal::Data *GlobalData = 0;
|
||||
|
||||
namespace Global {
|
||||
|
||||
|
@ -383,7 +424,7 @@ namespace Global {
|
|||
}
|
||||
|
||||
void start() {
|
||||
GlobalData = new GlobalDataStruct();
|
||||
GlobalData = new internal::Data();
|
||||
|
||||
memset_rand(&GlobalData->LaunchId, sizeof(GlobalData->LaunchId));
|
||||
}
|
||||
|
@ -394,10 +435,13 @@ namespace Global {
|
|||
}
|
||||
|
||||
DefineReadOnlyVar(Global, uint64, LaunchId);
|
||||
DefineRefVar(Global, SingleDelayedCall, HandleHistoryUpdate);
|
||||
|
||||
DefineVar(Global, Adaptive::Layout, AdaptiveLayout);
|
||||
DefineVar(Global, bool, AdaptiveForWide);
|
||||
|
||||
DefineVar(Global, int32, DebugLoggingFlags);
|
||||
|
||||
// config
|
||||
DefineVar(Global, int32, ChatSizeMax);
|
||||
DefineVar(Global, int32, MegagroupSizeMax);
|
||||
|
@ -417,4 +461,14 @@ namespace Global {
|
|||
|
||||
DefineVar(Global, HiddenPinnedMessagesMap, HiddenPinnedMessages);
|
||||
|
||||
DefineRefVar(Global, PendingItemsMap, PendingRepaintItems);
|
||||
|
||||
DefineVar(Global, Stickers::Sets, StickerSets);
|
||||
DefineVar(Global, Stickers::Order, StickerSetsOrder);
|
||||
DefineVar(Global, uint64, LastStickersUpdate);
|
||||
|
||||
DefineVar(Global, MTP::DcOptions, DcOptions);
|
||||
|
||||
DefineRefVar(Global, CircleMasksMap, CircleMasks);
|
||||
|
||||
};
|
||||
|
|
|
@ -53,6 +53,7 @@ namespace Ui {
|
|||
void repaintHistoryItem(const HistoryItem *item);
|
||||
void repaintInlineItem(const LayoutInlineItem *layout);
|
||||
bool isInlineItemVisible(const LayoutInlineItem *reader);
|
||||
void autoplayMediaInlineAsync(const FullMsgId &msgId);
|
||||
|
||||
void showPeerHistory(const PeerId &peer, MsgId msgId, bool back = false);
|
||||
inline void showPeerHistory(const PeerData *peer, MsgId msgId, bool back = false) {
|
||||
|
@ -68,6 +69,9 @@ namespace Ui {
|
|||
inline void showChatsList() {
|
||||
showPeerHistory(PeerId(0), 0);
|
||||
}
|
||||
inline void showChatsListAsync() {
|
||||
showPeerHistoryAsync(PeerId(0), 0);
|
||||
}
|
||||
|
||||
bool hideWindowNoQuit();
|
||||
|
||||
|
@ -90,14 +94,13 @@ namespace Notify {
|
|||
|
||||
void clipStopperHidden(ClipStopperType type);
|
||||
|
||||
void historyItemResized(const HistoryItem *item, bool scrollToIt = false);
|
||||
inline void historyItemsResized() {
|
||||
historyItemResized(0);
|
||||
}
|
||||
void historyItemLayoutChanged(const HistoryItem *item);
|
||||
|
||||
void automaticLoadSettingsChangedGif();
|
||||
|
||||
// handle pending resize() / paint() on history items
|
||||
void handlePendingHistoryUpdate();
|
||||
|
||||
};
|
||||
|
||||
#define DeclareReadOnlyVar(Type, Name) const Type &Name();
|
||||
|
@ -131,6 +134,30 @@ namespace Adaptive {
|
|||
};
|
||||
};
|
||||
|
||||
namespace DebugLogging {
|
||||
enum Flags {
|
||||
FileLoaderFlag = 0x00000001,
|
||||
};
|
||||
}
|
||||
|
||||
namespace Stickers {
|
||||
static const uint64 DefaultSetId = 0; // for backward compatibility
|
||||
static const uint64 CustomSetId = 0xFFFFFFFFFFFFFFFFULL, RecentSetId = 0xFFFFFFFFFFFFFFFEULL;
|
||||
static const uint64 NoneSetId = 0xFFFFFFFFFFFFFFFDULL; // for emoji/stickers panel
|
||||
struct Set {
|
||||
Set(uint64 id, uint64 access, const QString &title, const QString &shortName, int32 count, int32 hash, MTPDstickerSet::Flags flags) : id(id), access(access), title(title), shortName(shortName), count(count), hash(hash), flags(flags) {
|
||||
}
|
||||
uint64 id, access;
|
||||
QString title, shortName;
|
||||
int32 count, hash;
|
||||
MTPDstickerSet::Flags flags;
|
||||
StickerPack stickers;
|
||||
StickersByEmojiMap emoji;
|
||||
};
|
||||
typedef QMap<uint64, Set> Sets;
|
||||
typedef QList<uint64> Order;
|
||||
}
|
||||
|
||||
namespace Global {
|
||||
|
||||
bool started();
|
||||
|
@ -138,10 +165,13 @@ namespace Global {
|
|||
void finish();
|
||||
|
||||
DeclareReadOnlyVar(uint64, LaunchId);
|
||||
DeclareRefVar(SingleDelayedCall, HandleHistoryUpdate);
|
||||
|
||||
DeclareVar(Adaptive::Layout, AdaptiveLayout);
|
||||
DeclareVar(bool, AdaptiveForWide);
|
||||
|
||||
DeclareVar(int32, DebugLoggingFlags);
|
||||
|
||||
// config
|
||||
DeclareVar(int32, ChatSizeMax);
|
||||
DeclareVar(int32, MegagroupSizeMax);
|
||||
|
@ -162,6 +192,18 @@ namespace Global {
|
|||
typedef QMap<PeerId, MsgId> HiddenPinnedMessagesMap;
|
||||
DeclareVar(HiddenPinnedMessagesMap, HiddenPinnedMessages);
|
||||
|
||||
typedef OrderedSet<HistoryItem*> PendingItemsMap;
|
||||
DeclareRefVar(PendingItemsMap, PendingRepaintItems);
|
||||
|
||||
DeclareVar(Stickers::Sets, StickerSets);
|
||||
DeclareVar(Stickers::Order, StickerSetsOrder);
|
||||
DeclareVar(uint64, LastStickersUpdate);
|
||||
|
||||
DeclareVar(MTP::DcOptions, DcOptions);
|
||||
|
||||
typedef QMap<uint64, QPixmap> CircleMasksMap;
|
||||
DeclareRefVar(CircleMasksMap, CircleMasks);
|
||||
|
||||
};
|
||||
|
||||
namespace Adaptive {
|
||||
|
@ -175,3 +217,9 @@ namespace Adaptive {
|
|||
return Global::AdaptiveForWide() && (Global::AdaptiveLayout() == WideLayout);
|
||||
}
|
||||
}
|
||||
|
||||
namespace DebugLogging {
|
||||
inline bool FileLoader() {
|
||||
return (Global::DebugLoggingFlags() | FileLoaderFlag) != 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ void FileUploader::currentFailed() {
|
|||
|
||||
void FileUploader::killSessions() {
|
||||
for (int i = 0; i < MTPUploadSessionsCount; ++i) {
|
||||
MTP::stopSession(MTP::upl(i));
|
||||
MTP::stopSession(MTP::uplDcId(i));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,9 +187,9 @@ void FileUploader::sendNext() {
|
|||
}
|
||||
mtpRequestId requestId;
|
||||
if (i->docSize > UseBigFilesFrom) {
|
||||
requestId = MTP::send(MTPupload_SaveBigFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_int(i->docPartsCount), MTP_string(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::upl(todc));
|
||||
requestId = MTP::send(MTPupload_SaveBigFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_int(i->docPartsCount), MTP_string(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::uplDcId(todc));
|
||||
} else {
|
||||
requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_string(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::upl(todc));
|
||||
requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(i->id()), MTP_int(i->docSentParts), MTP_string(toSend)), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::uplDcId(todc));
|
||||
}
|
||||
docRequestsSent.insert(requestId, i->docSentParts);
|
||||
dcMap.insert(requestId, todc);
|
||||
|
@ -200,7 +200,7 @@ void FileUploader::sendNext() {
|
|||
} else {
|
||||
UploadFileParts::iterator part = parts.begin();
|
||||
|
||||
mtpRequestId requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(partsOfId), MTP_int(part.key()), MTP_string(part.value())), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::upl(todc));
|
||||
mtpRequestId requestId = MTP::send(MTPupload_SaveFilePart(MTP_long(partsOfId), MTP_int(part.key()), MTP_string(part.value())), rpcDone(&FileUploader::partLoaded), rpcFail(&FileUploader::partFailed), MTP::uplDcId(todc));
|
||||
requestsSent.insert(requestId, part.value());
|
||||
dcMap.insert(requestId, todc);
|
||||
sentSize += part.value().size();
|
||||
|
@ -246,7 +246,7 @@ void FileUploader::clear() {
|
|||
dcMap.clear();
|
||||
sentSize = 0;
|
||||
for (int32 i = 0; i < MTPUploadSessionsCount; ++i) {
|
||||
MTP::stopSession(MTP::upl(i));
|
||||
MTP::stopSession(MTP::uplDcId(i));
|
||||
sentSizes[i] = 0;
|
||||
}
|
||||
killSessionsTimer.stop();
|
||||
|
|
|
@ -22,6 +22,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "animation.h"
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/opt.h>
|
||||
#include <libswscale/swscale.h>
|
||||
}
|
||||
|
||||
#include "mainwidget.h"
|
||||
#include "window.h"
|
||||
|
||||
|
@ -245,7 +252,7 @@ ClipReader::ClipReader(const FileLocation &location, const QByteArray &data, Cal
|
|||
_clipManagers.push_back(new ClipReadManager(_clipThreads.back()));
|
||||
_clipThreads.back()->start();
|
||||
} else {
|
||||
_threadIndex = int32(MTP::nonce<uint32>() % _clipThreads.size());
|
||||
_threadIndex = int32(rand_value<uint32>() % _clipThreads.size());
|
||||
int32 loadLevel = 0x7FFFFFFF;
|
||||
for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) {
|
||||
int32 level = _clipManagers.at(i)->loadLevel();
|
||||
|
|
|
@ -208,7 +208,7 @@ class AnimationCreator {
|
|||
public:
|
||||
AnimationCreator(AnimationImplementation *ptr) : _ptr(ptr) {}
|
||||
AnimationCreator(const AnimationCreator &other) : _ptr(other.create()) {}
|
||||
AnimationImplementation *create() const { return exchange(_ptr); }
|
||||
AnimationImplementation *create() const { return getPointerAndReset(_ptr); }
|
||||
~AnimationCreator() { deleteAndMark(_ptr); }
|
||||
private:
|
||||
AnimationCreator &operator=(const AnimationCreator &other);
|
||||
|
|
|
@ -149,7 +149,7 @@ bool _filedialogGetFiles(QStringList &files, QByteArray &remoteContent, const QS
|
|||
cSetDialogLastPath(path);
|
||||
Local::writeUserSettings();
|
||||
}
|
||||
|
||||
|
||||
if (res == QDialog::Accepted) {
|
||||
if (multipleFiles > 0) {
|
||||
files = dialog.selectedFiles();
|
||||
|
@ -157,9 +157,9 @@ bool _filedialogGetFiles(QStringList &files, QByteArray &remoteContent, const QS
|
|||
files = dialog.selectedFiles().mid(0, 1);
|
||||
}
|
||||
if (multipleFiles >= 0) {
|
||||
#ifdef Q_OS_WIN
|
||||
#if defined Q_OS_WIN && !defined Q_OS_WINRT
|
||||
remoteContent = dialog.selectedRemoteContent();
|
||||
#endif
|
||||
#endif // Q_OS_WIN && !Q_OS_WINRT
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -392,7 +392,7 @@ void FlatTextarea::onMentionHashtagOrBotCommandInsert(QString str) {
|
|||
QString t(fr.text());
|
||||
for (int i = pos - p; i > 0; --i) {
|
||||
if (t.at(i - 1) == '@' || t.at(i - 1) == '#' || t.at(i - 1) == '/') {
|
||||
if ((i == pos - p || t.at(i).isLetter() || t.at(i - 1) == '#') && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
|
||||
if ((i == pos - p || (t.at(i - 1) == '/' ? t.at(i).isLetterOrNumber() : t.at(i).isLetter()) || t.at(i - 1) == '#') && (i < 2 || !(t.at(i - 2).isLetterOrNumber() || t.at(i - 2) == '_'))) {
|
||||
c.setPosition(p + i - 1, QTextCursor::MoveAnchor);
|
||||
int till = p + i;
|
||||
for (; (till < e) && (till - p - i + 1 < str.size()); ++till) {
|
||||
|
|
|
@ -47,6 +47,7 @@ namespace {
|
|||
static const uint64 ColoredCacheSkip = 0x2000000000000000LLU;
|
||||
static const uint64 BlurredColoredCacheSkip = 0x3000000000000000LLU;
|
||||
static const uint64 RoundedCacheSkip = 0x4000000000000000LLU;
|
||||
static const uint64 CircledCacheSkip = 0x5000000000000000LLU;
|
||||
}
|
||||
|
||||
StorageImageLocation StorageImageLocation::Null;
|
||||
|
@ -106,7 +107,7 @@ const QPixmap &Image::pix(int32 w, int32 h) const {
|
|||
uint64 k = (uint64(w) << 32) | uint64(h);
|
||||
Sizes::const_iterator i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
QPixmap p(pixNoCache(w, h, true));
|
||||
QPixmap p(pixNoCache(w, h, ImagePixSmooth));
|
||||
if (cRetina()) p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
|
@ -128,7 +129,29 @@ const QPixmap &Image::pixRounded(int32 w, int32 h) const {
|
|||
uint64 k = RoundedCacheSkip | (uint64(w) << 32) | uint64(h);
|
||||
Sizes::const_iterator i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
QPixmap p(pixNoCache(w, h, true, false, true));
|
||||
QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixRounded));
|
||||
if (cRetina()) p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
globalAcquiredSize += int64(p.width()) * p.height() * 4;
|
||||
}
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixCircled(int32 w, int32 h) const {
|
||||
checkload();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
w = width();
|
||||
} else if (cRetina()) {
|
||||
w *= cIntRetinaFactor();
|
||||
h *= cIntRetinaFactor();
|
||||
}
|
||||
uint64 k = CircledCacheSkip | (uint64(w) << 32) | uint64(h);
|
||||
Sizes::const_iterator i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixCircled));
|
||||
if (cRetina()) p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
|
@ -150,7 +173,7 @@ const QPixmap &Image::pixBlurred(int32 w, int32 h) const {
|
|||
uint64 k = BlurredCacheSkip | (uint64(w) << 32) | uint64(h);
|
||||
Sizes::const_iterator i = _sizesCache.constFind(k);
|
||||
if (i == _sizesCache.cend()) {
|
||||
QPixmap p(pixNoCache(w, h, true, true));
|
||||
QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixBlurred));
|
||||
if (cRetina()) p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
|
@ -219,7 +242,7 @@ const QPixmap &Image::pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) co
|
|||
if (i != _sizesCache.cend()) {
|
||||
globalAcquiredSize -= int64(i->width()) * i->height() * 4;
|
||||
}
|
||||
QPixmap p(pixNoCache(w, h, true, false, true, outerw, outerh));
|
||||
QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixRounded, outerw, outerh));
|
||||
if (cRetina()) p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
|
@ -229,7 +252,7 @@ const QPixmap &Image::pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) co
|
|||
return i.value();
|
||||
}
|
||||
|
||||
const QPixmap &Image::pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 outerh) const {
|
||||
const QPixmap &Image::pixBlurredSingle(int w, int h, int32 outerw, int32 outerh) const {
|
||||
checkload();
|
||||
|
||||
if (w <= 0 || !width() || !height()) {
|
||||
|
@ -244,7 +267,7 @@ const QPixmap &Image::pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 out
|
|||
if (i != _sizesCache.cend()) {
|
||||
globalAcquiredSize -= int64(i->width()) * i->height() * 4;
|
||||
}
|
||||
QPixmap p(pixNoCache(w, h, true, true, true, outerw, outerh));
|
||||
QPixmap p(pixNoCache(w, h, ImagePixSmooth | ImagePixBlurred | ImagePixRounded, outerw, outerh));
|
||||
if (cRetina()) p.setDevicePixelRatio(cRetinaFactor());
|
||||
i = _sizesCache.insert(k, p);
|
||||
if (!p.isNull()) {
|
||||
|
@ -375,6 +398,43 @@ yi += stride;
|
|||
return img;
|
||||
}
|
||||
|
||||
const QPixmap &circleMask(int width, int height) {
|
||||
t_assert(Global::started());
|
||||
|
||||
uint64 key = uint64(uint32(width)) << 32 | uint64(uint32(height));
|
||||
|
||||
Global::CircleMasksMap &masks(Global::RefCircleMasks());
|
||||
auto i = masks.constFind(key);
|
||||
if (i == masks.cend()) {
|
||||
QImage mask(width, height, QImage::Format_ARGB32_Premultiplied);
|
||||
mask.fill(st::transparent);
|
||||
{
|
||||
Painter p(&mask);
|
||||
p.setRenderHint(QPainter::HighQualityAntialiasing);
|
||||
p.setCompositionMode(QPainter::CompositionMode_SourceOver);
|
||||
p.setBrush(st::white);
|
||||
p.setPen(Qt::NoPen);
|
||||
p.drawEllipse(0, 0, width, height);
|
||||
}
|
||||
mask.setDevicePixelRatio(cRetinaFactor());
|
||||
i = masks.insert(key, QPixmap::fromImage(mask));
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
void imageCircle(QImage &img) {
|
||||
t_assert(!img.isNull());
|
||||
|
||||
img.setDevicePixelRatio(cRetinaFactor());
|
||||
img = img.convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
t_assert(!img.isNull());
|
||||
|
||||
QPixmap mask = circleMask(img.width(), img.height());
|
||||
Painter p(&img);
|
||||
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
|
||||
p.drawPixmap(0, 0, mask);
|
||||
}
|
||||
|
||||
void imageRound(QImage &img) {
|
||||
t_assert(!img.isNull());
|
||||
|
||||
|
@ -435,18 +495,18 @@ QImage imageColored(const style::color &add, QImage img) {
|
|||
return img;
|
||||
}
|
||||
|
||||
QPixmap imagePix(QImage img, int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh) {
|
||||
QPixmap imagePix(QImage img, int32 w, int32 h, ImagePixOptions options, int32 outerw, int32 outerh) {
|
||||
t_assert(!img.isNull());
|
||||
if (blurred) {
|
||||
if (options.testFlag(ImagePixBlurred)) {
|
||||
img = imageBlur(img);
|
||||
t_assert(!img.isNull());
|
||||
}
|
||||
if (w <= 0 || (w == img.width() && (h <= 0 || h == img.height()))) {
|
||||
} else if (h <= 0) {
|
||||
img = img.scaledToWidth(w, smooth ? Qt::SmoothTransformation : Qt::FastTransformation);
|
||||
img = img.scaledToWidth(w, options.testFlag(ImagePixSmooth) ? Qt::SmoothTransformation : Qt::FastTransformation);
|
||||
t_assert(!img.isNull());
|
||||
} else {
|
||||
img = img.scaled(w, h, Qt::IgnoreAspectRatio, smooth ? Qt::SmoothTransformation : Qt::FastTransformation);
|
||||
img = img.scaled(w, h, Qt::IgnoreAspectRatio, options.testFlag(ImagePixSmooth) ? Qt::SmoothTransformation : Qt::FastTransformation);
|
||||
t_assert(!img.isNull());
|
||||
}
|
||||
if (outerw > 0 && outerh > 0) {
|
||||
|
@ -467,7 +527,10 @@ QPixmap imagePix(QImage img, int32 w, int32 h, bool smooth, bool blurred, bool r
|
|||
t_assert(!img.isNull());
|
||||
}
|
||||
}
|
||||
if (rounded) {
|
||||
if (options.testFlag(ImagePixCircled)) {
|
||||
imageCircle(img);
|
||||
t_assert(!img.isNull());
|
||||
} else if (options.testFlag(ImagePixRounded)) {
|
||||
imageRound(img);
|
||||
t_assert(!img.isNull());
|
||||
}
|
||||
|
@ -475,7 +538,7 @@ QPixmap imagePix(QImage img, int32 w, int32 h, bool smooth, bool blurred, bool r
|
|||
return QPixmap::fromImage(img, Qt::ColorOnly);
|
||||
}
|
||||
|
||||
QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh) const {
|
||||
QPixmap Image::pixNoCache(int w, int h, ImagePixOptions options, int outerw, int outerh) const {
|
||||
if (!loading()) const_cast<Image*>(this)->load();
|
||||
restore();
|
||||
|
||||
|
@ -483,7 +546,7 @@ QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth, bool blurred, bool roun
|
|||
if (h <= 0 && height() > 0) {
|
||||
h = qRound(width() * w / float64(height()));
|
||||
}
|
||||
return blank()->pixNoCache(w, h, smooth, blurred, rounded, outerw, outerh);
|
||||
return blank()->pixNoCache(w, h, options, outerw, outerh);
|
||||
}
|
||||
|
||||
if (isNull() && outerw > 0 && outerh > 0) {
|
||||
|
@ -506,11 +569,15 @@ QPixmap Image::pixNoCache(int32 w, int32 h, bool smooth, bool blurred, bool roun
|
|||
p.fillRect(qMax(0, (outerw - w) / 2), qMax(0, (outerh - h) / 2), qMin(result.width(), w), qMin(result.height(), h), st::white);
|
||||
}
|
||||
|
||||
if (rounded) imageRound(result);
|
||||
if (options.testFlag(ImagePixCircled)) {
|
||||
imageCircle(result);
|
||||
} else if (options.testFlag(ImagePixRounded)) {
|
||||
imageRound(result);
|
||||
}
|
||||
return QPixmap::fromImage(result, Qt::ColorOnly);
|
||||
}
|
||||
|
||||
return imagePix(_data.toImage(), w, h, smooth, blurred, rounded, outerw, outerh);
|
||||
return imagePix(_data.toImage(), w, h, options, outerw, outerh);
|
||||
}
|
||||
|
||||
QPixmap Image::pixColoredNoCache(const style::color &add, int32 w, int32 h, bool smooth) const {
|
||||
|
|
|
@ -107,7 +107,15 @@ inline bool operator!=(const StorageImageLocation &a, const StorageImageLocation
|
|||
return !(a == b);
|
||||
}
|
||||
|
||||
QPixmap imagePix(QImage img, int32 w, int32 h, bool smooth, bool blurred, bool rounded, int32 outerw, int32 outerh);
|
||||
enum ImagePixOption {
|
||||
ImagePixSmooth = 0x01,
|
||||
ImagePixBlurred = 0x02,
|
||||
ImagePixRounded = 0x04,
|
||||
ImagePixCircled = 0x08,
|
||||
};
|
||||
Q_DECLARE_FLAGS(ImagePixOptions, ImagePixOption);
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(ImagePixOptions);
|
||||
QPixmap imagePix(QImage img, int w, int h, ImagePixOptions options, int outerw, int outerh);
|
||||
|
||||
class DelayedStorageImage;
|
||||
|
||||
|
@ -145,12 +153,13 @@ public:
|
|||
|
||||
const QPixmap &pix(int32 w = 0, int32 h = 0) const;
|
||||
const QPixmap &pixRounded(int32 w = 0, int32 h = 0) const;
|
||||
const QPixmap &pixCircled(int32 w = 0, int32 h = 0) const;
|
||||
const QPixmap &pixBlurred(int32 w = 0, int32 h = 0) const;
|
||||
const QPixmap &pixColored(const style::color &add, int32 w = 0, int32 h = 0) const;
|
||||
const QPixmap &pixBlurredColored(const style::color &add, int32 w = 0, int32 h = 0) const;
|
||||
const QPixmap &pixSingle(int32 w, int32 h, int32 outerw, int32 outerh) const;
|
||||
const QPixmap &pixBlurredSingle(int32 w, int32 h, int32 outerw, int32 outerh) const;
|
||||
QPixmap pixNoCache(int32 w = 0, int32 h = 0, bool smooth = false, bool blurred = false, bool rounded = false, int32 outerw = -1, int32 outerh = -1) const;
|
||||
QPixmap pixNoCache(int w = 0, int h = 0, ImagePixOptions options = 0, int outerw = -1, int outerh = -1) const;
|
||||
QPixmap pixColoredNoCache(const style::color &add, int32 w = 0, int32 h = 0, bool smooth = false) const;
|
||||
QPixmap pixBlurredColoredNoCache(const style::color &add, int32 w, int32 h = 0) const;
|
||||
|
||||
|
|
|
@ -561,7 +561,7 @@ void PopupTooltip::onShow() {
|
|||
}
|
||||
|
||||
void PopupTooltip::onWndActiveChanged() {
|
||||
if (!App::wnd()->windowHandle()->isActive()) {
|
||||
if (!App::wnd() || !App::wnd()->windowHandle() || !App::wnd()->windowHandle()->isActive()) {
|
||||
PopupTooltip::Hide();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1654,6 +1654,7 @@ public:
|
|||
*_getSymbolUpon = true;
|
||||
return false;
|
||||
} else if (_p) {
|
||||
#ifndef TDESKTOP_WINRT // temp
|
||||
QTextCharFormat format;
|
||||
QTextItemInt gf(glyphs.mid(glyphsStart, glyphsEnd - glyphsStart),
|
||||
&_e->fnt, engine.layoutData->string.unicode() + itemStart,
|
||||
|
@ -1662,7 +1663,7 @@ public:
|
|||
gf.width = itemWidth;
|
||||
gf.justified = false;
|
||||
gf.initWithScriptItem(si);
|
||||
|
||||
#endif // !TDESKTOP_WINRT
|
||||
if (_localFrom + itemStart < _selectedTo && _localFrom + itemEnd > _selectedFrom) {
|
||||
QFixed selX = x, selWidth = itemWidth;
|
||||
if (_localFrom + itemEnd > _selectedTo || _localFrom + itemStart < _selectedFrom) {
|
||||
|
@ -1703,7 +1704,9 @@ public:
|
|||
_p->fillRect(QRectF(selX.toReal(), _y + _yDelta, selWidth.toReal(), _fontHeight), _textStyle->selectBg->b);
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_WINRT // temp
|
||||
_p->drawTextItem(QPointF(x.toReal(), textY), gf);
|
||||
#endif // !TDESKTOP_WINRT
|
||||
}
|
||||
|
||||
x += itemWidth;
|
||||
|
@ -2531,20 +2534,32 @@ Text::Text(style::font font, const QString &text, const TextParseOptions &option
|
|||
}
|
||||
}
|
||||
|
||||
Text::Text(const Text &other) :
|
||||
_minResizeWidth(other._minResizeWidth), _maxWidth(other._maxWidth),
|
||||
_minHeight(other._minHeight),
|
||||
_text(other._text),
|
||||
_font(other._font),
|
||||
_blocks(other._blocks.size()),
|
||||
_links(other._links),
|
||||
_startDir(other._startDir)
|
||||
{
|
||||
Text::Text(const Text &other)
|
||||
: _minResizeWidth(other._minResizeWidth)
|
||||
, _maxWidth(other._maxWidth)
|
||||
, _minHeight(other._minHeight)
|
||||
, _text(other._text)
|
||||
, _font(other._font)
|
||||
, _blocks(other._blocks.size())
|
||||
, _links(other._links)
|
||||
, _startDir(other._startDir) {
|
||||
for (int32 i = 0, l = _blocks.size(); i < l; ++i) {
|
||||
_blocks[i] = other._blocks.at(i)->clone();
|
||||
}
|
||||
}
|
||||
|
||||
Text::Text(Text &&other)
|
||||
: _minResizeWidth(other._minResizeWidth)
|
||||
, _maxWidth(other._maxWidth)
|
||||
, _minHeight(other._minHeight)
|
||||
, _text(other._text)
|
||||
, _font(other._font)
|
||||
, _blocks(other._blocks)
|
||||
, _links(other._links)
|
||||
, _startDir(other._startDir) {
|
||||
other.clearFields();
|
||||
}
|
||||
|
||||
Text &Text::operator=(const Text &other) {
|
||||
_minResizeWidth = other._minResizeWidth;
|
||||
_maxWidth = other._maxWidth;
|
||||
|
@ -2560,10 +2575,23 @@ Text &Text::operator=(const Text &other) {
|
|||
return *this;
|
||||
}
|
||||
|
||||
Text &Text::operator=(Text &&other) {
|
||||
_minResizeWidth = other._minResizeWidth;
|
||||
_maxWidth = other._maxWidth;
|
||||
_minHeight = other._minHeight;
|
||||
_text = other._text;
|
||||
_font = other._font;
|
||||
_blocks = other._blocks;
|
||||
_links = other._links;
|
||||
_startDir = other._startDir;
|
||||
other.clearFields();
|
||||
return *this;
|
||||
}
|
||||
|
||||
void Text::setText(style::font font, const QString &text, const TextParseOptions &options) {
|
||||
if (!_textStyle) _initDefault();
|
||||
_font = font;
|
||||
clean();
|
||||
clear();
|
||||
{
|
||||
TextParser parser(this, text, options);
|
||||
}
|
||||
|
@ -2641,7 +2669,7 @@ void Text::recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir) {
|
|||
void Text::setMarkedText(style::font font, const QString &text, const EntitiesInText &entities, const TextParseOptions &options) {
|
||||
if (!_textStyle) _initDefault();
|
||||
_font = font;
|
||||
clean();
|
||||
clear();
|
||||
{
|
||||
// QString newText; // utf16 of the text for emoji
|
||||
// newText.reserve(8 * text.size());
|
||||
|
@ -3213,10 +3241,14 @@ EntitiesInText Text::originalEntities() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
void Text::clean() {
|
||||
void Text::clear() {
|
||||
for (TextBlocks::iterator i = _blocks.begin(), e = _blocks.end(); i != e; ++i) {
|
||||
delete *i;
|
||||
}
|
||||
clearFields();
|
||||
}
|
||||
|
||||
void Text::clearFields() {
|
||||
_blocks.clear();
|
||||
_links.clear();
|
||||
_maxWidth = _minHeight = 0;
|
||||
|
@ -3468,7 +3500,7 @@ TextBlock::TextBlock(const style::font &font, const QString &str, QFixed minResi
|
|||
if (length) {
|
||||
style::font blockFont = font;
|
||||
if (!flags && lnkIndex) {
|
||||
// should use textStyle lnkFlags somehow.. not supported
|
||||
// should use textStyle lnkFlags somehow... not supported
|
||||
}
|
||||
|
||||
if ((flags & TextBlockFPre) || (flags & TextBlockFCode)) {
|
||||
|
@ -4559,6 +4591,7 @@ namespace {
|
|||
case 65359: return QChar(111);
|
||||
case 65363: return QChar(115);
|
||||
case 65367: return QChar(119);
|
||||
case 1105: return QChar(1077);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -598,7 +598,9 @@ public:
|
|||
Text(int32 minResizeWidth = QFIXED_MAX);
|
||||
Text(style::font font, const QString &text, const TextParseOptions &options = _defaultOptions, int32 minResizeWidth = QFIXED_MAX, bool richText = false);
|
||||
Text(const Text &other);
|
||||
Text(Text &&other);
|
||||
Text &operator=(const Text &other);
|
||||
Text &operator=(Text &&other);
|
||||
|
||||
int32 countWidth(int32 width) const;
|
||||
int32 countHeight(int32 width) const;
|
||||
|
@ -686,15 +688,19 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
void clean();
|
||||
void clear();
|
||||
~Text() {
|
||||
clean();
|
||||
clear();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
void recountNaturalSize(bool initial, Qt::LayoutDirection optionsDir = Qt::LayoutDirectionAuto);
|
||||
|
||||
// clear() deletes all blocks and calls this method
|
||||
// it is also called from move constructor / assignment operator
|
||||
void clearFields();
|
||||
|
||||
QFixed _minResizeWidth, _maxWidth;
|
||||
int32 _minHeight;
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -66,7 +66,7 @@ public:
|
|||
void touchScrollUpdated(const QPoint &screenPos);
|
||||
QPoint mapMouseToItem(QPoint p, HistoryItem *item);
|
||||
|
||||
int32 recountHeight(const HistoryItem *resizedItem);
|
||||
void recountHeight();
|
||||
void updateSize();
|
||||
|
||||
void repaintItem(const HistoryItem *item);
|
||||
|
@ -88,11 +88,15 @@ public:
|
|||
|
||||
HistoryItem *atTopImportantMsg(int32 top, int32 height, int32 &bottomUnderScrollTop) const;
|
||||
|
||||
int32 historyHeight() const;
|
||||
int32 migratedTop() const;
|
||||
int32 historyTop() const;
|
||||
int32 historyDrawTop() const;
|
||||
int32 itemTop(const HistoryItem *item) const; // -1 if should not be visible, -2 if bad history()
|
||||
// updates history->scrollTopItem/scrollTopOffset
|
||||
void visibleAreaUpdated(int top, int bottom);
|
||||
|
||||
int historyHeight() const;
|
||||
int historyScrollTop() const;
|
||||
int migratedTop() const;
|
||||
int historyTop() const;
|
||||
int historyDrawTop() const;
|
||||
int itemTop(const HistoryItem *item) const; // -1 if should not be visible, -2 if bad history()
|
||||
|
||||
void notifyIsBotChanged();
|
||||
void notifyMigrateUpdated();
|
||||
|
@ -135,22 +139,30 @@ private:
|
|||
HistoryItem *nextItem(HistoryItem *item);
|
||||
void updateDragSelection(HistoryItem *dragSelFrom, HistoryItem *dragSelTo, bool dragSelecting, bool force = false);
|
||||
|
||||
PeerData *_peer;
|
||||
History *_migrated, *_history;
|
||||
int32 _historyOffset, _historySkipHeight; // height of first date and first sys msg
|
||||
PeerData *_peer = nullptr;
|
||||
History *_migrated = nullptr;
|
||||
History *_history = nullptr;
|
||||
int _historyOffset = 0;
|
||||
|
||||
BotInfo *_botInfo;
|
||||
int32 _botDescWidth, _botDescHeight;
|
||||
// with migrated history we perhaps do not need to display first _history message
|
||||
// (if last _migrated message and first _history message are both isGroupMigrate)
|
||||
// or at least we don't need to display first _history date (just skip it by height)
|
||||
int _historySkipHeight = 0;
|
||||
|
||||
BotInfo *_botInfo = nullptr;
|
||||
int _botDescWidth = 0;
|
||||
int _botDescHeight = 0;
|
||||
QRect _botDescRect;
|
||||
|
||||
HistoryWidget *_widget;
|
||||
ScrollArea *_scroll;
|
||||
mutable History *_curHistory;
|
||||
mutable int32 _curBlock, _curItem;
|
||||
HistoryWidget *_widget = nullptr;
|
||||
ScrollArea *_scroll = nullptr;
|
||||
mutable History *_curHistory = nullptr;
|
||||
mutable int _curBlock = 0;
|
||||
mutable int _curItem = 0;
|
||||
|
||||
bool _firstLoading;
|
||||
bool _firstLoading = false;
|
||||
|
||||
Qt::CursorShape _cursor;
|
||||
style::cursor _cursor = style::cur_default;
|
||||
typedef QMap<HistoryItem*, uint32> SelectedItems;
|
||||
SelectedItems _selected;
|
||||
void applyDragSelection();
|
||||
|
@ -164,34 +176,60 @@ private:
|
|||
PrepareSelect = 0x03,
|
||||
Selecting = 0x04,
|
||||
};
|
||||
DragAction _dragAction;
|
||||
TextSelectType _dragSelType;
|
||||
DragAction _dragAction = NoDrag;
|
||||
TextSelectType _dragSelType = TextSelectLetters;
|
||||
QPoint _dragStartPos, _dragPos;
|
||||
HistoryItem *_dragItem;
|
||||
HistoryCursorState _dragCursorState;
|
||||
uint16 _dragSymbol;
|
||||
bool _dragWasInactive;
|
||||
HistoryItem *_dragItem = nullptr;
|
||||
HistoryCursorState _dragCursorState = HistoryDefaultCursorState;
|
||||
uint16 _dragSymbol = 0;
|
||||
bool _dragWasInactive = false;
|
||||
|
||||
QPoint _trippleClickPoint;
|
||||
QTimer _trippleClickTimer;
|
||||
|
||||
TextLinkPtr _contextMenuLnk;
|
||||
|
||||
HistoryItem *_dragSelFrom, *_dragSelTo;
|
||||
bool _dragSelecting;
|
||||
bool _wasSelectedText; // was some text selected in current drag action
|
||||
HistoryItem *_dragSelFrom = nullptr;
|
||||
HistoryItem *_dragSelTo = nullptr;
|
||||
bool _dragSelecting = false;
|
||||
bool _wasSelectedText = false; // was some text selected in current drag action
|
||||
|
||||
bool _touchScroll, _touchSelect, _touchInProgress;
|
||||
// scroll by touch support (at least Windows Surface tablets)
|
||||
bool _touchScroll = false;
|
||||
bool _touchSelect = false;
|
||||
bool _touchInProgress = false;
|
||||
QPoint _touchStart, _touchPrevPos, _touchPos;
|
||||
QTimer _touchSelectTimer;
|
||||
|
||||
TouchScrollState _touchScrollState;
|
||||
bool _touchPrevPosValid, _touchWaitingAcceleration;
|
||||
TouchScrollState _touchScrollState = TouchScrollManual;
|
||||
bool _touchPrevPosValid = false;
|
||||
bool _touchWaitingAcceleration = false;
|
||||
QPoint _touchSpeed;
|
||||
uint64 _touchSpeedTime, _touchAccelerationTime, _touchTime;
|
||||
uint64 _touchSpeedTime = 0;
|
||||
uint64 _touchAccelerationTime = 0;
|
||||
uint64 _touchTime = 0;
|
||||
QTimer _touchScrollTimer;
|
||||
|
||||
PopupMenu *_menu;
|
||||
// context menu
|
||||
PopupMenu *_menu = nullptr;
|
||||
|
||||
// save visible area coords for painting / pressing userpics
|
||||
int _visibleAreaTop = 0;
|
||||
int _visibleAreaBottom = 0;
|
||||
|
||||
// this function finds all userpics on the left that are displayed and calls template method
|
||||
// for each found userpic (from the bottom to the top) in the passed history with passed top offset
|
||||
//
|
||||
// method has "bool (*Method)(HistoryMessage *message, int userpicTop)" signature
|
||||
// if it returns false the enumeration stops immidiately
|
||||
template <typename Method>
|
||||
void enumerateUserpicsInHistory(History *h, int htop, Method method);
|
||||
|
||||
template <typename Method>
|
||||
void enumerateUserpics(Method method) {
|
||||
enumerateUserpicsInHistory(_history, historyTop(), method);
|
||||
enumerateUserpicsInHistory(_migrated, migratedTop(), method);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
@ -589,13 +627,13 @@ public:
|
|||
bool ui_isInlineItemBeingChosen();
|
||||
|
||||
void notify_historyItemLayoutChanged(const HistoryItem *item);
|
||||
void notify_automaticLoadSettingsChangedGif();
|
||||
void notify_botCommandsChanged(UserData *user);
|
||||
void notify_inlineBotRequesting(bool requesting);
|
||||
void notify_userIsBotChanged(UserData *user);
|
||||
void notify_migrateUpdated(PeerData *peer);
|
||||
void notify_clipStopperHidden(ClipStopperType type);
|
||||
void notify_historyItemResized(const HistoryItem *item, bool scrollToIt);
|
||||
void notify_automaticLoadSettingsChangedGif();
|
||||
void notify_handlePendingHistoryUpdate();
|
||||
|
||||
void cmd_search();
|
||||
void cmd_next_chat();
|
||||
|
@ -646,7 +684,7 @@ public slots:
|
|||
void onReportSpamHide();
|
||||
void onReportSpamClear();
|
||||
|
||||
void onListScroll();
|
||||
void onScroll();
|
||||
void onHistoryToEnd();
|
||||
void onCollapseComments();
|
||||
void onSend(bool ctrlShiftEnter = false, MsgId replyTo = -1);
|
||||
|
@ -676,7 +714,7 @@ public slots:
|
|||
void onPhotoSend(PhotoData *photo);
|
||||
void onInlineResultSend(InlineResult *result, UserData *bot);
|
||||
|
||||
void onVisibleChanged();
|
||||
void onWindowVisibleChanged();
|
||||
|
||||
void deleteMessage();
|
||||
void forwardMessage();
|
||||
|
@ -709,16 +747,20 @@ public slots:
|
|||
|
||||
void onUpdateHistoryItems();
|
||||
|
||||
// checks if we are too close to the top or to the bottom
|
||||
// in the scroll area and preloads history if needed
|
||||
void preloadHistoryIfNeeded();
|
||||
|
||||
private:
|
||||
|
||||
MsgId _replyToId;
|
||||
MsgId _replyToId = 0;
|
||||
Text _replyToName;
|
||||
int32 _replyToNameVersion;
|
||||
int _replyToNameVersion = 0;
|
||||
void updateReplyToName();
|
||||
|
||||
MsgId _editMsgId;
|
||||
MsgId _editMsgId = 0;
|
||||
|
||||
HistoryItem *_replyEditMsg;
|
||||
HistoryItem *_replyEditMsg = nullptr;
|
||||
Text _replyEditMsgText;
|
||||
|
||||
IconedButton _fieldBarCancel;
|
||||
|
@ -727,13 +769,13 @@ private:
|
|||
struct PinnedBar {
|
||||
PinnedBar(MsgId msgId, HistoryWidget *parent);
|
||||
|
||||
MsgId msgId;
|
||||
HistoryItem *msg;
|
||||
MsgId msgId = 0;
|
||||
HistoryItem *msg = nullptr;
|
||||
Text text;
|
||||
IconedButton cancel;
|
||||
PlainShadow shadow;
|
||||
};
|
||||
PinnedBar *_pinnedBar;
|
||||
PinnedBar *_pinnedBar = nullptr;
|
||||
void updatePinnedBar(bool force = false);
|
||||
bool pinnedMsgVisibilityUpdated();
|
||||
void destroyPinnedBar();
|
||||
|
@ -754,32 +796,36 @@ private:
|
|||
|
||||
void updateMouseTracking();
|
||||
|
||||
mtpRequestId _saveEditMsgRequestId;
|
||||
// destroys _history and _migrated unread bars
|
||||
void destroyUnreadBar();
|
||||
|
||||
mtpRequestId _saveEditMsgRequestId = 0;
|
||||
void saveEditMsg();
|
||||
void saveEditMsgDone(History *history, const MTPUpdates &updates, mtpRequestId req);
|
||||
bool saveEditMsgFail(History *history, const RPCError &error, mtpRequestId req);
|
||||
|
||||
DBIPeerReportSpamStatus _reportSpamStatus;
|
||||
mtpRequestId _reportSpamSettingRequestId;
|
||||
static const mtpRequestId ReportSpamRequestNeeded = -1;
|
||||
DBIPeerReportSpamStatus _reportSpamStatus = dbiprsUnknown;
|
||||
mtpRequestId _reportSpamSettingRequestId = ReportSpamRequestNeeded;
|
||||
void updateReportSpamStatus();
|
||||
void requestReportSpamSetting();
|
||||
void reportSpamSettingDone(const MTPPeerSettings &result, mtpRequestId req);
|
||||
bool reportSpamSettingFail(const RPCError &error, mtpRequestId req);
|
||||
|
||||
QString _previewLinks;
|
||||
WebPageData *_previewData;
|
||||
WebPageData *_previewData = nullptr;
|
||||
typedef QMap<QString, WebPageId> PreviewCache;
|
||||
PreviewCache _previewCache;
|
||||
mtpRequestId _previewRequest;
|
||||
Text _previewTitle, _previewDescription;
|
||||
mtpRequestId _previewRequest = 0;
|
||||
Text _previewTitle;
|
||||
Text _previewDescription;
|
||||
SingleTimer _previewTimer;
|
||||
bool _previewCancelled;
|
||||
bool _previewCancelled = false;
|
||||
void gotPreview(QString links, const MTPMessageMedia &media, mtpRequestId req);
|
||||
|
||||
bool _replyForwardPressed;
|
||||
bool _replyForwardPressed = false;
|
||||
|
||||
HistoryItem *_replyReturn;
|
||||
HistoryItem *_replyReturn = nullptr;
|
||||
QList<MsgId> _replyReturns;
|
||||
|
||||
bool messagesFailed(const RPCError &error, mtpRequestId requestId);
|
||||
|
@ -795,7 +841,7 @@ private:
|
|||
ScrollChangeType type;
|
||||
int value;
|
||||
};
|
||||
void updateListSize(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 }, const HistoryItem *resizedItem = 0, bool scrollToIt = false);
|
||||
void updateListSize(bool initial = false, bool loadedDown = false, const ScrollChange &change = { ScrollChangeNone, 0 });
|
||||
|
||||
void saveGifDone(DocumentData *doc, const MTPBool &result);
|
||||
|
||||
|
@ -811,11 +857,11 @@ private:
|
|||
|
||||
void countHistoryShowFrom();
|
||||
|
||||
mtpRequestId _stickersUpdateRequest;
|
||||
mtpRequestId _stickersUpdateRequest = 0;
|
||||
void stickersGot(const MTPmessages_AllStickers &stickers);
|
||||
bool stickersFailed(const RPCError &error);
|
||||
|
||||
mtpRequestId _savedGifsUpdateRequest;
|
||||
mtpRequestId _savedGifsUpdateRequest = 0;
|
||||
void savedGifsGot(const MTPmessages_SavedGifs &gifs);
|
||||
bool savedGifsFailed(const RPCError &error);
|
||||
|
||||
|
@ -827,39 +873,51 @@ private:
|
|||
|
||||
void updateDragAreas();
|
||||
|
||||
// when scroll position or scroll area size changed this method
|
||||
// updates the boundings of the visible area in HistoryInner
|
||||
void visibleAreaUpdated();
|
||||
|
||||
bool readyToForward() const;
|
||||
bool hasBroadcastToggle() const;
|
||||
bool hasSilentToggle() const;
|
||||
|
||||
PeerData *_peer, *_clearPeer; // cache _peer in _clearPeer when showing clear history box
|
||||
ChannelId _channel;
|
||||
bool _canSendMessages;
|
||||
MsgId _showAtMsgId, _fixedInScrollMsgId;
|
||||
int32 _fixedInScrollMsgTop;
|
||||
PeerData *_peer = nullptr;
|
||||
|
||||
mtpRequestId _firstLoadRequest, _preloadRequest, _preloadDownRequest;
|
||||
// cache current _peer in _clearPeer when showing clear history box
|
||||
PeerData *_clearPeer = nullptr;
|
||||
|
||||
MsgId _delayedShowAtMsgId;
|
||||
mtpRequestId _delayedShowAtRequest;
|
||||
ChannelId _channel = NoChannel;
|
||||
bool _canSendMessages = false;
|
||||
MsgId _showAtMsgId = ShowAtUnreadMsgId;
|
||||
MsgId _fixedInScrollMsgId = 0;
|
||||
int32 _fixedInScrollMsgTop = 0;
|
||||
|
||||
MsgId _activeAnimMsgId;
|
||||
mtpRequestId _firstLoadRequest = 0;
|
||||
mtpRequestId _preloadRequest = 0;
|
||||
mtpRequestId _preloadDownRequest = 0;
|
||||
|
||||
MsgId _delayedShowAtMsgId = -1; // wtf?
|
||||
mtpRequestId _delayedShowAtRequest = 0;
|
||||
|
||||
MsgId _activeAnimMsgId = 0;
|
||||
|
||||
ScrollArea _scroll;
|
||||
HistoryInner *_list;
|
||||
History *_migrated, *_history;
|
||||
bool _histInited; // initial updateListSize() called
|
||||
HistoryInner *_list = nullptr;
|
||||
History *_migrated = nullptr;
|
||||
History *_history = nullptr;
|
||||
bool _histInited = false; // initial updateListSize() called
|
||||
|
||||
int32 _lastScroll;
|
||||
uint64 _lastScrolled;
|
||||
int32 _lastScroll = 0;
|
||||
uint64 _lastScrolled = 0;
|
||||
QTimer _updateHistoryItems; // gifs optimization
|
||||
|
||||
IconedButton _toHistoryEnd;
|
||||
CollapseButton _collapseComments;
|
||||
|
||||
MentionsDropdown _attachMention;
|
||||
UserData *_inlineBot;
|
||||
UserData *_inlineBot = nullptr;
|
||||
QString _inlineBotUsername;
|
||||
mtpRequestId _inlineBotResolveRequestId;
|
||||
mtpRequestId _inlineBotResolveRequestId = 0;
|
||||
void inlineBotResolveDone(const MTPcontacts_ResolvedPeer &result);
|
||||
bool inlineBotResolveFail(QString name, const RPCError &error);
|
||||
|
||||
|
@ -872,46 +930,52 @@ private:
|
|||
ReportSpamPanel _reportSpamPanel;
|
||||
|
||||
FlatButton _send, _unblock, _botStart, _joinChannel, _muteUnmute;
|
||||
mtpRequestId _unblockRequest, _reportSpamRequest;
|
||||
mtpRequestId _unblockRequest = 0;
|
||||
mtpRequestId _reportSpamRequest = 0;
|
||||
IconedButton _attachDocument, _attachPhoto;
|
||||
EmojiButton _attachEmoji;
|
||||
IconedButton _kbShow, _kbHide, _cmdStart;
|
||||
FlatCheckbox _broadcast;
|
||||
SilentToggle _silent;
|
||||
bool _cmdStartShown;
|
||||
bool _cmdStartShown = false;
|
||||
MessageField _field;
|
||||
Animation _a_record, _a_recording;
|
||||
bool _recording, _inRecord, _inField, _inReplyEdit, _inPinnedMsg;
|
||||
anim::ivalue a_recordingLevel;
|
||||
int32 _recordingSamples;
|
||||
anim::fvalue a_recordOver, a_recordDown;
|
||||
bool _recording = false;
|
||||
bool _inRecord = false;
|
||||
bool _inField = false;
|
||||
bool _inReplyEdit = false;
|
||||
bool _inPinnedMsg = false;
|
||||
anim::ivalue a_recordingLevel = { 0, 0 };
|
||||
int32 _recordingSamples = 0;
|
||||
anim::fvalue a_recordOver = { 0, 0 };
|
||||
anim::fvalue a_recordDown = { 0, 0 };
|
||||
anim::cvalue a_recordCancel;
|
||||
int32 _recordCancelWidth;
|
||||
|
||||
bool kbWasHidden() const;
|
||||
|
||||
bool _kbShown;
|
||||
HistoryItem *_kbReplyTo;
|
||||
bool _kbShown = false;
|
||||
HistoryItem *_kbReplyTo = nullptr;
|
||||
ScrollArea _kbScroll;
|
||||
BotKeyboard _keyboard;
|
||||
|
||||
Dropdown _attachType;
|
||||
EmojiPan _emojiPan;
|
||||
DragState _attachDrag;
|
||||
DragState _attachDrag = DragStateNone;
|
||||
DragArea _attachDragDocument, _attachDragPhoto;
|
||||
|
||||
int32 _selCount; // < 0 - text selected, focus list, not _field
|
||||
|
||||
TaskQueue _fileLoader;
|
||||
int32 _textUpdateEventsFlags;
|
||||
int32 _textUpdateEventsFlags = (TextUpdateEventsSaveDraft | TextUpdateEventsSendTyping);
|
||||
|
||||
int64 _serviceImageCacheSize;
|
||||
int64 _serviceImageCacheSize = 0;
|
||||
QString _confirmSource;
|
||||
|
||||
uint64 _confirmWithTextId;
|
||||
uint64 _confirmWithTextId = 0;
|
||||
|
||||
QString _titlePeerText;
|
||||
int32 _titlePeerTextWidth;
|
||||
int32 _titlePeerTextWidth = 0;
|
||||
|
||||
Animation _a_show;
|
||||
QPixmap _cacheUnder, _cacheOver, _cacheTopBarUnder, _cacheTopBarOver;
|
||||
|
@ -919,20 +983,20 @@ private:
|
|||
anim::fvalue a_shadow;
|
||||
|
||||
QTimer _scrollTimer;
|
||||
int32 _scrollDelta;
|
||||
int32 _scrollDelta = 0;
|
||||
|
||||
QTimer _animActiveTimer;
|
||||
float64 _animActiveStart;
|
||||
float64 _animActiveStart = 0;
|
||||
|
||||
QMap<QPair<History*, SendActionType>, mtpRequestId> _sendActionRequests;
|
||||
QTimer _sendActionStopTimer;
|
||||
|
||||
uint64 _saveDraftStart;
|
||||
bool _saveDraftText;
|
||||
uint64 _saveDraftStart = 0;
|
||||
bool _saveDraftText = false;
|
||||
QTimer _saveDraftTimer;
|
||||
|
||||
PlainShadow _sideShadow, _topShadow;
|
||||
bool _inGrab;
|
||||
bool _inGrab = false;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "application.h"
|
||||
|
||||
#include "intro/introcode.h"
|
||||
#include "intro/intro.h"
|
||||
#include "intro/introsignup.h"
|
||||
#include "intro/intropwdcheck.h"
|
||||
|
||||
CodeInput::CodeInput(QWidget *parent, const style::flatInput &st, const QString &ph) : FlatInput(parent, st, ph) {
|
||||
}
|
||||
|
@ -72,7 +73,7 @@ void CodeInput::correctValue(const QString &was, QString &now) {
|
|||
if (strict) emit codeEntered();
|
||||
}
|
||||
|
||||
IntroCode::IntroCode(IntroWidget *parent) : IntroStage(parent)
|
||||
IntroCode::IntroCode(IntroWidget *parent) : IntroStep(parent)
|
||||
, a_errorAlpha(0)
|
||||
, _a_error(animation(this, &IntroCode::step_error))
|
||||
, next(this, lang(lng_intro_next), st::btnIntroNext)
|
||||
|
@ -80,11 +81,10 @@ IntroCode::IntroCode(IntroWidget *parent) : IntroStage(parent)
|
|||
, _noTelegramCode(this, lang(lng_code_no_telegram), st::introLink)
|
||||
, _noTelegramCodeRequestId(0)
|
||||
, code(this, st::inpIntroCode, lang(lng_code_ph))
|
||||
, waitTillCall(intro()->getCallTimeout()) {
|
||||
setVisible(false);
|
||||
, sentRequest(0)
|
||||
, callStatus(intro()->getCallStatus()) {
|
||||
setGeometry(parent->innerRect());
|
||||
|
||||
connect(&next, SIGNAL(stateChanged(int, ButtonStateChangeSource)), parent, SLOT(onDoneStateChanged(int, ButtonStateChangeSource)));
|
||||
connect(&next, SIGNAL(clicked()), this, SLOT(onSubmitCode()));
|
||||
connect(&code, SIGNAL(changed()), this, SLOT(onInputChange()));
|
||||
connect(&callTimer, SIGNAL(timeout()), this, SLOT(onSendCall()));
|
||||
|
@ -92,6 +92,12 @@ IntroCode::IntroCode(IntroWidget *parent) : IntroStage(parent)
|
|||
connect(&_noTelegramCode, SIGNAL(clicked()), this, SLOT(onNoTelegramCode()));
|
||||
|
||||
updateDescText();
|
||||
|
||||
if (!intro()->codeByTelegram()) {
|
||||
if (callStatus.type == IntroWidget::CallWaiting) {
|
||||
callTimer.start(1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IntroCode::updateDescText() {
|
||||
|
@ -101,8 +107,8 @@ void IntroCode::updateDescText() {
|
|||
callTimer.stop();
|
||||
} else {
|
||||
_noTelegramCode.hide();
|
||||
waitTillCall = intro()->getCallTimeout();
|
||||
if (!callTimer.isActive()) {
|
||||
callStatus = intro()->getCallStatus();
|
||||
if (callStatus.type == IntroWidget::CallWaiting && !callTimer.isActive()) {
|
||||
callTimer.start(1000);
|
||||
}
|
||||
}
|
||||
|
@ -125,15 +131,27 @@ void IntroCode::paintEvent(QPaintEvent *e) {
|
|||
}
|
||||
if (codeByTelegram) {
|
||||
} else {
|
||||
QString callText = lang(lng_code_calling);
|
||||
if (waitTillCall >= 3600) {
|
||||
callText = lng_code_call(lt_minutes, qsl("%1:%2").arg(waitTillCall / 3600).arg((waitTillCall / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(waitTillCall % 60, 2, 10, QChar('0')));
|
||||
} else if (waitTillCall > 0) {
|
||||
callText = lng_code_call(lt_minutes, QString::number(waitTillCall / 60), lt_seconds, qsl("%1").arg(waitTillCall % 60, 2, 10, QChar('0')));
|
||||
} else if (waitTillCall < 0) {
|
||||
QString callText;
|
||||
switch (callStatus.type) {
|
||||
case IntroWidget::CallWaiting: {
|
||||
if (callStatus.timeout >= 3600) {
|
||||
callText = lng_code_call(lt_minutes, qsl("%1:%2").arg(callStatus.timeout / 3600).arg((callStatus.timeout / 60) % 60, 2, 10, QChar('0')), lt_seconds, qsl("%1").arg(callStatus.timeout % 60, 2, 10, QChar('0')));
|
||||
} else {
|
||||
callText = lng_code_call(lt_minutes, QString::number(callStatus.timeout / 60), lt_seconds, qsl("%1").arg(callStatus.timeout % 60, 2, 10, QChar('0')));
|
||||
}
|
||||
} break;
|
||||
|
||||
case IntroWidget::CallCalling: {
|
||||
callText = lang(lng_code_calling);
|
||||
} break;
|
||||
|
||||
case IntroWidget::CallCalled: {
|
||||
callText = lang(lng_code_called);
|
||||
} break;
|
||||
}
|
||||
if (!callText.isEmpty()) {
|
||||
p.drawText(QRect(textRect.left(), code.y() + code.height() + st::introCallSkip, st::introTextSize.width(), st::introErrHeight), callText, style::al_center);
|
||||
}
|
||||
p.drawText(QRect(textRect.left(), code.y() + code.height() + st::introCallSkip, st::introTextSize.width(), st::introErrHeight), callText, style::al_center);
|
||||
}
|
||||
if (_a_error.animating() || error.length()) {
|
||||
p.setOpacity(a_errorAlpha.current());
|
||||
|
@ -172,7 +190,7 @@ void IntroCode::step_error(float64 ms, bool timer) {
|
|||
_a_error.stop();
|
||||
a_errorAlpha.finish();
|
||||
if (!a_errorAlpha.current()) {
|
||||
error = "";
|
||||
error.clear();
|
||||
}
|
||||
} else {
|
||||
a_errorAlpha.update(dt, st::introErrFunc);
|
||||
|
@ -181,30 +199,29 @@ void IntroCode::step_error(float64 ms, bool timer) {
|
|||
}
|
||||
|
||||
void IntroCode::activate() {
|
||||
waitTillCall = intro()->getCallTimeout();
|
||||
if (!intro()->codeByTelegram()) {
|
||||
callTimer.start(1000);
|
||||
}
|
||||
error = "";
|
||||
a_errorAlpha = anim::fvalue(0);
|
||||
sentCode = QString();
|
||||
show();
|
||||
code.setDisabled(false);
|
||||
IntroStep::activate();
|
||||
code.setFocus();
|
||||
}
|
||||
|
||||
void IntroCode::prepareShow() {
|
||||
void IntroCode::finished() {
|
||||
IntroStep::finished();
|
||||
error.clear();
|
||||
a_errorAlpha = anim::fvalue(0);
|
||||
|
||||
sentCode.clear();
|
||||
code.setDisabled(false);
|
||||
|
||||
callTimer.stop();
|
||||
code.setText(QString());
|
||||
rpcClear();
|
||||
}
|
||||
|
||||
void IntroCode::cancelled() {
|
||||
if (sentRequest) {
|
||||
MTP::cancel(sentRequest);
|
||||
sentRequest = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void IntroCode::deactivate() {
|
||||
callTimer.stop();
|
||||
hide();
|
||||
code.clearFocus();
|
||||
MTP::send(MTPauth_CancelCode(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash())));
|
||||
}
|
||||
|
||||
void IntroCode::stopCheck() {
|
||||
|
@ -218,9 +235,9 @@ void IntroCode::onCheckRequest() {
|
|||
if (leftms >= 1000) {
|
||||
if (sentRequest) {
|
||||
MTP::cancel(sentRequest);
|
||||
sentCode = "";
|
||||
sentRequest = 0;
|
||||
sentCode.clear();
|
||||
}
|
||||
sentRequest = 0;
|
||||
if (!code.isEnabled()) {
|
||||
code.setDisabled(false);
|
||||
code.setFocus();
|
||||
|
@ -234,6 +251,7 @@ void IntroCode::onCheckRequest() {
|
|||
|
||||
void IntroCode::codeSubmitDone(const MTPauth_Authorization &result) {
|
||||
stopCheck();
|
||||
sentRequest = 0;
|
||||
code.setDisabled(false);
|
||||
const MTPDauth_authorization &d(result.c_auth_authorization());
|
||||
if (d.vuser.type() != mtpc_user || !d.vuser.c_user().is_self()) { // wtf?
|
||||
|
@ -246,10 +264,11 @@ void IntroCode::codeSubmitDone(const MTPauth_Authorization &result) {
|
|||
|
||||
bool IntroCode::codeSubmitFail(const RPCError &error) {
|
||||
stopCheck();
|
||||
sentRequest = 0;
|
||||
code.setDisabled(false);
|
||||
const QString &err = error.type();
|
||||
if (err == "PHONE_NUMBER_INVALID" || err == "PHONE_CODE_EXPIRED") { // show error
|
||||
onBack();
|
||||
intro()->onBack();
|
||||
return true;
|
||||
} else if (err == "PHONE_CODE_EMPTY" || err == "PHONE_CODE_INVALID") {
|
||||
showError(lang(lng_bad_code));
|
||||
|
@ -257,7 +276,7 @@ bool IntroCode::codeSubmitFail(const RPCError &error) {
|
|||
return true;
|
||||
} else if (err == "PHONE_NUMBER_UNOCCUPIED") { // success, need to signUp
|
||||
intro()->setCode(sentCode);
|
||||
intro()->onIntroNext();
|
||||
intro()->replaceStep(new IntroSignup(intro()));
|
||||
return true;
|
||||
} else if (err == "SESSION_PASSWORD_NEEDED") {
|
||||
intro()->setCode(sentCode);
|
||||
|
@ -280,27 +299,34 @@ bool IntroCode::codeSubmitFail(const RPCError &error) {
|
|||
}
|
||||
|
||||
void IntroCode::onInputChange() {
|
||||
showError("");
|
||||
showError(QString());
|
||||
if (code.text().length() == 5) onSubmitCode();
|
||||
}
|
||||
|
||||
void IntroCode::onSendCall() {
|
||||
if (!--waitTillCall) {
|
||||
callTimer.stop();
|
||||
MTP::send(MTPauth_SendCall(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash())), rpcDone(&IntroCode::callDone));
|
||||
if (callStatus.type == IntroWidget::CallWaiting) {
|
||||
if (--callStatus.timeout <= 0) {
|
||||
callStatus.type = IntroWidget::CallCalling;
|
||||
callTimer.stop();
|
||||
MTP::send(MTPauth_ResendCode(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash())), rpcDone(&IntroCode::callDone));
|
||||
} else {
|
||||
intro()->setCallStatus(callStatus);
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void IntroCode::callDone(const MTPBool &v) {
|
||||
if (!waitTillCall) {
|
||||
waitTillCall = -1;
|
||||
void IntroCode::callDone(const MTPauth_SentCode &v) {
|
||||
if (callStatus.type == IntroWidget::CallCalling) {
|
||||
callStatus.type = IntroWidget::CallCalled;
|
||||
intro()->setCallStatus(callStatus);
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
void IntroCode::gotPassword(const MTPaccount_Password &result) {
|
||||
stopCheck();
|
||||
sentRequest = 0;
|
||||
code.setDisabled(false);
|
||||
switch (result.type()) {
|
||||
case mtpc_account_noPassword: // should not happen
|
||||
|
@ -312,18 +338,18 @@ void IntroCode::gotPassword(const MTPaccount_Password &result) {
|
|||
intro()->setPwdSalt(qba(d.vcurrent_salt));
|
||||
intro()->setHasRecovery(mtpIsTrue(d.vhas_recovery));
|
||||
intro()->setPwdHint(qs(d.vhint));
|
||||
intro()->onIntroNext();
|
||||
intro()->replaceStep(new IntroPwdCheck(intro()));
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void IntroCode::onSubmitCode(bool force) {
|
||||
if (!force && (code.text() == sentCode || !code.isEnabled())) return;
|
||||
void IntroCode::onSubmitCode() {
|
||||
if (sentRequest) return;
|
||||
|
||||
code.setDisabled(true);
|
||||
setFocus();
|
||||
|
||||
showError("");
|
||||
showError(QString());
|
||||
|
||||
checkRequest.start(1000);
|
||||
|
||||
|
@ -336,10 +362,27 @@ void IntroCode::onSubmitCode(bool force) {
|
|||
|
||||
void IntroCode::onNoTelegramCode() {
|
||||
if (_noTelegramCodeRequestId) return;
|
||||
_noTelegramCodeRequestId = MTP::send(MTPauth_SendSms(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash())), rpcDone(&IntroCode::noTelegramCodeDone), rpcFail(&IntroCode::noTelegramCodeFail));
|
||||
_noTelegramCodeRequestId = MTP::send(MTPauth_ResendCode(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash())), rpcDone(&IntroCode::noTelegramCodeDone), rpcFail(&IntroCode::noTelegramCodeFail));
|
||||
}
|
||||
|
||||
void IntroCode::noTelegramCodeDone(const MTPBool &result) {
|
||||
void IntroCode::noTelegramCodeDone(const MTPauth_SentCode &result) {
|
||||
if (result.type() != mtpc_auth_sentCode) {
|
||||
showError(lang(lng_server_error));
|
||||
return;
|
||||
}
|
||||
|
||||
const MTPDauth_sentCode &d(result.c_auth_sentCode());
|
||||
switch (d.vtype.type()) {
|
||||
case mtpc_auth_sentCodeTypeApp: intro()->setCodeByTelegram(true);
|
||||
case mtpc_auth_sentCodeTypeSms:
|
||||
case mtpc_auth_sentCodeTypeCall: intro()->setCodeByTelegram(false);
|
||||
case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break;
|
||||
}
|
||||
if (d.has_next_type() && d.vnext_type.type() == mtpc_auth_codeTypeCall) {
|
||||
intro()->setCallStatus({ IntroWidget::CallWaiting, d.has_timeout() ? d.vtimeout.v : 60 });
|
||||
} else {
|
||||
intro()->setCallStatus({ IntroWidget::CallDisabled, 0 });
|
||||
}
|
||||
intro()->setCodeByTelegram(false);
|
||||
updateDescText();
|
||||
}
|
||||
|
@ -359,10 +402,6 @@ bool IntroCode::noTelegramCodeFail(const RPCError &error) {
|
|||
return false;
|
||||
}
|
||||
|
||||
void IntroCode::onNext() {
|
||||
void IntroCode::onSubmit() {
|
||||
onSubmitCode();
|
||||
}
|
||||
|
||||
void IntroCode::onBack() {
|
||||
intro()->onIntroBack();
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include <QtWidgets/QWidget>
|
||||
#include "gui/flatbutton.h"
|
||||
#include "gui/flatinput.h"
|
||||
#include "intro.h"
|
||||
#include "intro/introwidget.h"
|
||||
|
||||
class CodeInput : public FlatInput {
|
||||
class CodeInput final : public FlatInput {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
@ -42,27 +42,25 @@ protected:
|
|||
|
||||
};
|
||||
|
||||
class IntroCode : public IntroStage, public RPCSender {
|
||||
class IntroCode final : public IntroStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
IntroCode(IntroWidget *parent);
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void step_error(float64 ms, bool timer);
|
||||
|
||||
void activate();
|
||||
void prepareShow();
|
||||
void deactivate();
|
||||
void onNext();
|
||||
void onBack();
|
||||
|
||||
bool hasBack() const {
|
||||
bool hasBack() const override {
|
||||
return true;
|
||||
}
|
||||
void activate() override;
|
||||
void finished() override;
|
||||
void cancelled() override;
|
||||
void onSubmit() override;
|
||||
|
||||
void codeSubmitDone(const MTPauth_Authorization &result);
|
||||
bool codeSubmitFail(const RPCError &error);
|
||||
|
@ -71,7 +69,7 @@ public:
|
|||
|
||||
public slots:
|
||||
|
||||
void onSubmitCode(bool force = false);
|
||||
void onSubmitCode();
|
||||
void onNoTelegramCode();
|
||||
void onInputChange();
|
||||
void onSendCall();
|
||||
|
@ -80,7 +78,7 @@ public slots:
|
|||
private:
|
||||
|
||||
void showError(const QString &err);
|
||||
void callDone(const MTPBool &v);
|
||||
void callDone(const MTPauth_SentCode &v);
|
||||
void gotPassword(const MTPaccount_Password &result);
|
||||
|
||||
void stopCheck();
|
||||
|
@ -96,14 +94,14 @@ private:
|
|||
mtpRequestId _noTelegramCodeRequestId;
|
||||
QRect textRect;
|
||||
|
||||
void noTelegramCodeDone(const MTPBool &result);
|
||||
void noTelegramCodeDone(const MTPauth_SentCode &result);
|
||||
bool noTelegramCodeFail(const RPCError &result);
|
||||
|
||||
CodeInput code;
|
||||
QString sentCode;
|
||||
mtpRequestId sentRequest;
|
||||
QTimer callTimer;
|
||||
int32 waitTillCall;
|
||||
IntroWidget::CallStatus callStatus;
|
||||
|
||||
QTimer checkRequest;
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "application.h"
|
||||
|
||||
#include "intro/introphone.h"
|
||||
#include "intro/intro.h"
|
||||
#include "intro/introcode.h"
|
||||
|
||||
namespace {
|
||||
class SignUpLink : public ITextLink {
|
||||
|
@ -45,7 +45,7 @@ namespace {
|
|||
};
|
||||
}
|
||||
|
||||
IntroPhone::IntroPhone(IntroWidget *parent) : IntroStage(parent)
|
||||
IntroPhone::IntroPhone(IntroWidget *parent) : IntroStep(parent)
|
||||
, a_errorAlpha(0)
|
||||
, _a_error(animation(this, &IntroPhone::step_error))
|
||||
, changed(false)
|
||||
|
@ -54,11 +54,11 @@ IntroPhone::IntroPhone(IntroWidget *parent) : IntroStage(parent)
|
|||
, phone(this, st::inpIntroPhone)
|
||||
, code(this, st::inpIntroCountryCode)
|
||||
, _signup(this, lng_phone_notreg(lt_signup_start, textcmdStartLink(1), lt_signup_end, textcmdStopLink()), st::introErrLabel, st::introErrLabelTextStyle)
|
||||
, _showSignup(false) {
|
||||
, _showSignup(false)
|
||||
, sentRequest(0) {
|
||||
setVisible(false);
|
||||
setGeometry(parent->innerRect());
|
||||
|
||||
connect(&next, SIGNAL(stateChanged(int, ButtonStateChangeSource)), parent, SLOT(onDoneStateChanged(int, ButtonStateChangeSource)));
|
||||
connect(&next, SIGNAL(clicked()), this, SLOT(onSubmitPhone()));
|
||||
connect(&phone, SIGNAL(voidBackspace(QKeyEvent*)), &code, SLOT(startErasing(QKeyEvent*)));
|
||||
connect(&country, SIGNAL(codeChanged(const QString &)), &code, SLOT(codeSelected(const QString &)));
|
||||
|
@ -145,7 +145,7 @@ void IntroPhone::step_error(float64 ms, bool timer) {
|
|||
_a_error.stop();
|
||||
a_errorAlpha.finish();
|
||||
if (!a_errorAlpha.current()) {
|
||||
error = "";
|
||||
error.clear();
|
||||
_signup.hide();
|
||||
} else if (!error.isEmpty() && _showSignup) {
|
||||
_signup.show();
|
||||
|
@ -164,7 +164,7 @@ void IntroPhone::countryChanged() {
|
|||
|
||||
void IntroPhone::onInputChange() {
|
||||
changed = true;
|
||||
showError("");
|
||||
showError(QString());
|
||||
}
|
||||
|
||||
void IntroPhone::disableAll() {
|
||||
|
@ -183,8 +183,8 @@ void IntroPhone::enableAll(bool failed) {
|
|||
if (failed) phone.setFocus();
|
||||
}
|
||||
|
||||
void IntroPhone::onSubmitPhone(bool force) {
|
||||
if (!force && !next.isEnabled()) return;
|
||||
void IntroPhone::onSubmitPhone() {
|
||||
if (sentRequest || isHidden()) return;
|
||||
|
||||
if (!App::isValidPhone(fullNumber())) {
|
||||
showError(lang(lng_bad_phone));
|
||||
|
@ -193,7 +193,7 @@ void IntroPhone::onSubmitPhone(bool force) {
|
|||
}
|
||||
|
||||
disableAll();
|
||||
showError("");
|
||||
showError(QString());
|
||||
|
||||
checkRequest.start(1000);
|
||||
|
||||
|
@ -226,45 +226,58 @@ void IntroPhone::phoneCheckDone(const MTPauth_CheckedPhone &result) {
|
|||
const MTPDauth_checkedPhone &d(result.c_auth_checkedPhone());
|
||||
if (mtpIsTrue(d.vphone_registered)) {
|
||||
disableAll();
|
||||
showError("");
|
||||
showError(QString());
|
||||
|
||||
checkRequest.start(1000);
|
||||
|
||||
sentRequest = MTP::send(MTPauth_SendCode(MTP_string(sentPhone), MTP_int(5), MTP_int(ApiId), MTP_string(ApiHash), MTP_string(Sandbox::LangSystemISO())), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail));
|
||||
MTPauth_SendCode::Flags flags = 0;
|
||||
sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(flags), MTP_string(sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash), MTP_string(Sandbox::LangSystemISO())), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail));
|
||||
} else {
|
||||
showError(lang(lng_bad_phone_noreg), true);
|
||||
enableAll(true);
|
||||
sentRequest = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void IntroPhone::phoneSubmitDone(const MTPauth_SentCode &result) {
|
||||
stopCheck();
|
||||
enableAll(false);
|
||||
sentRequest = 0;
|
||||
enableAll(true);
|
||||
|
||||
if (result.type() == mtpc_auth_sentCode) {
|
||||
const MTPDauth_sentCode &d(result.c_auth_sentCode());
|
||||
intro()->setPhone(sentPhone, d.vphone_code_hash.c_string().v.c_str(), mtpIsTrue(d.vphone_registered));
|
||||
intro()->setCallTimeout(d.vsend_call_timeout.v);
|
||||
} else if (result.type() == mtpc_auth_sentAppCode) {
|
||||
const MTPDauth_sentAppCode &d(result.c_auth_sentAppCode());
|
||||
intro()->setPhone(sentPhone, d.vphone_code_hash.c_string().v.c_str(), mtpIsTrue(d.vphone_registered));
|
||||
intro()->setCallTimeout(d.vsend_call_timeout.v);
|
||||
intro()->setCodeByTelegram(true);
|
||||
if (result.type() != mtpc_auth_sentCode) {
|
||||
showError(lang(lng_server_error));
|
||||
return;
|
||||
}
|
||||
intro()->onIntroNext();
|
||||
|
||||
const MTPDauth_sentCode &d(result.c_auth_sentCode());
|
||||
switch (d.vtype.type()) {
|
||||
case mtpc_auth_sentCodeTypeApp: intro()->setCodeByTelegram(true); break;
|
||||
case mtpc_auth_sentCodeTypeSms:
|
||||
case mtpc_auth_sentCodeTypeCall: intro()->setCodeByTelegram(false); break;
|
||||
case mtpc_auth_sentCodeTypeFlashCall: LOG(("Error: should not be flashcall!")); break;
|
||||
}
|
||||
intro()->setPhone(sentPhone, d.vphone_code_hash.c_string().v.c_str(), d.is_phone_registered());
|
||||
if (d.has_next_type() && d.vnext_type.type() == mtpc_auth_codeTypeCall) {
|
||||
intro()->setCallStatus({ IntroWidget::CallWaiting, d.has_timeout() ? d.vtimeout.v : 60 });
|
||||
} else {
|
||||
intro()->setCallStatus({ IntroWidget::CallDisabled, 0 });
|
||||
}
|
||||
intro()->nextStep(new IntroCode(intro()));
|
||||
}
|
||||
|
||||
void IntroPhone::toSignUp() {
|
||||
disableAll();
|
||||
showError("");
|
||||
showError(QString());
|
||||
|
||||
checkRequest.start(1000);
|
||||
|
||||
sentRequest = MTP::send(MTPauth_SendCode(MTP_string(sentPhone), MTP_int(0), MTP_int(ApiId), MTP_string(ApiHash), MTP_string(Sandbox::LangSystemISO())), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail));
|
||||
MTPauth_SendCode::Flags flags = 0;
|
||||
sentRequest = MTP::send(MTPauth_SendCode(MTP_flags(flags), MTP_string(sentPhone), MTPBool(), MTP_int(ApiId), MTP_string(ApiHash), MTP_string(Sandbox::LangSystemISO())), rpcDone(&IntroPhone::phoneSubmitDone), rpcFail(&IntroPhone::phoneSubmitFail));
|
||||
}
|
||||
|
||||
bool IntroPhone::phoneSubmitFail(const RPCError &error) {
|
||||
stopCheck();
|
||||
sentRequest = 0;
|
||||
const QString &err = error.type();
|
||||
if (err == "PHONE_NUMBER_INVALID") { // show error
|
||||
showError(lang(lng_bad_phone));
|
||||
|
@ -293,21 +306,27 @@ void IntroPhone::selectCountry(const QString &c) {
|
|||
}
|
||||
|
||||
void IntroPhone::activate() {
|
||||
error = "";
|
||||
IntroStep::activate();
|
||||
phone.setFocus();
|
||||
}
|
||||
|
||||
void IntroPhone::finished() {
|
||||
IntroStep::finished();
|
||||
checkRequest.stop();
|
||||
rpcClear();
|
||||
|
||||
error.clear();
|
||||
a_errorAlpha = anim::fvalue(0);
|
||||
show();
|
||||
enableAll(true);
|
||||
}
|
||||
|
||||
void IntroPhone::deactivate() {
|
||||
checkRequest.stop();
|
||||
hide();
|
||||
phone.clearFocus();
|
||||
void IntroPhone::cancelled() {
|
||||
if (sentRequest) {
|
||||
MTP::cancel(sentRequest);
|
||||
sentRequest = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void IntroPhone::onNext() {
|
||||
void IntroPhone::onSubmit() {
|
||||
onSubmitPhone();
|
||||
}
|
||||
|
||||
void IntroPhone::onBack() {
|
||||
}
|
||||
|
|
|
@ -23,26 +23,26 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include <QtWidgets/QWidget>
|
||||
#include "gui/flatbutton.h"
|
||||
#include "gui/countryinput.h"
|
||||
#include "intro.h"
|
||||
#include "intro/introwidget.h"
|
||||
|
||||
class IntroPhone : public IntroStage, public RPCSender {
|
||||
class IntroPhone final : public IntroStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
IntroPhone(IntroWidget *parent);
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void step_error(float64 ms, bool timer);
|
||||
|
||||
void selectCountry(const QString &country);
|
||||
|
||||
void activate();
|
||||
void deactivate();
|
||||
void onNext();
|
||||
void onBack();
|
||||
void activate() override;
|
||||
void finished() override;
|
||||
void cancelled() override;
|
||||
void onSubmit() override;
|
||||
|
||||
void phoneCheckDone(const MTPauth_CheckedPhone &result);
|
||||
void phoneSubmitDone(const MTPauth_SentCode &result);
|
||||
|
@ -54,7 +54,7 @@ public slots:
|
|||
|
||||
void countryChanged();
|
||||
void onInputChange();
|
||||
void onSubmitPhone(bool force = false);
|
||||
void onSubmitPhone();
|
||||
void onCheckRequest();
|
||||
|
||||
private:
|
||||
|
|
|
@ -19,6 +19,8 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "intro/intropwdcheck.h"
|
||||
|
||||
#include "lang.h"
|
||||
#include "style.h"
|
||||
|
||||
|
@ -27,10 +29,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "application.h"
|
||||
|
||||
#include "intro/intropwdcheck.h"
|
||||
#include "intro/intro.h"
|
||||
#include "intro/introsignup.h"
|
||||
|
||||
IntroPwdCheck::IntroPwdCheck(IntroWidget *parent) : IntroStage(parent)
|
||||
IntroPwdCheck::IntroPwdCheck(IntroWidget *parent) : IntroStep(parent)
|
||||
, a_errorAlpha(0)
|
||||
, _a_error(animation(this, &IntroPwdCheck::step_error))
|
||||
, _next(this, lang(lng_intro_submit), st::btnIntroNext)
|
||||
|
@ -130,7 +131,7 @@ void IntroPwdCheck::step_error(float64 ms, bool timer) {
|
|||
_a_error.stop();
|
||||
a_errorAlpha.finish();
|
||||
if (!a_errorAlpha.current()) {
|
||||
error = "";
|
||||
error.clear();
|
||||
}
|
||||
} else {
|
||||
a_errorAlpha.update(dt, st::introErrFunc);
|
||||
|
@ -139,7 +140,7 @@ void IntroPwdCheck::step_error(float64 ms, bool timer) {
|
|||
}
|
||||
|
||||
void IntroPwdCheck::activate() {
|
||||
show();
|
||||
IntroStep::activate();
|
||||
if (_pwdField.isHidden()) {
|
||||
_codeField.setFocus();
|
||||
} else {
|
||||
|
@ -147,8 +148,11 @@ void IntroPwdCheck::activate() {
|
|||
}
|
||||
}
|
||||
|
||||
void IntroPwdCheck::deactivate() {
|
||||
hide();
|
||||
void IntroPwdCheck::cancelled() {
|
||||
if (sentRequest) {
|
||||
MTP::cancel(sentRequest);
|
||||
sentRequest = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void IntroPwdCheck::stopCheck() {
|
||||
|
@ -202,7 +206,7 @@ bool IntroPwdCheck::pwdSubmitFail(const RPCError &error) {
|
|||
_pwdField.notaBene();
|
||||
return true;
|
||||
} else if (err == "PASSWORD_EMPTY") {
|
||||
intro()->onIntroBack();
|
||||
intro()->onBack();
|
||||
} else if (mtpIsFlood(error)) {
|
||||
showError(lang(lng_flood_error));
|
||||
_pwdField.notaBene();
|
||||
|
@ -224,7 +228,7 @@ bool IntroPwdCheck::codeSubmitFail(const RPCError &error) {
|
|||
_codeField.setDisabled(false);
|
||||
const QString &err = error.type();
|
||||
if (err == "PASSWORD_EMPTY") {
|
||||
intro()->onIntroBack();
|
||||
intro()->onBack();
|
||||
return true;
|
||||
} else if (err == "PASSWORD_RECOVERY_NA") {
|
||||
recoverStartFail(error);
|
||||
|
@ -265,7 +269,7 @@ bool IntroPwdCheck::recoverStartFail(const RPCError &error) {
|
|||
_codeField.hide();
|
||||
_pwdField.setFocus();
|
||||
update();
|
||||
showError("");
|
||||
showError(QString());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -275,7 +279,7 @@ void IntroPwdCheck::onToRecover() {
|
|||
MTP::cancel(sentRequest);
|
||||
sentRequest = 0;
|
||||
}
|
||||
showError("");
|
||||
showError(QString());
|
||||
_toRecover.hide();
|
||||
_toPassword.show();
|
||||
_pwdField.hide();
|
||||
|
@ -335,11 +339,11 @@ bool IntroPwdCheck::deleteFail(const RPCError &error) {
|
|||
|
||||
void IntroPwdCheck::deleteDone(const MTPBool &v) {
|
||||
Ui::hideLayer();
|
||||
intro()->onIntroNext();
|
||||
intro()->replaceStep(new IntroSignup(intro()));
|
||||
}
|
||||
|
||||
void IntroPwdCheck::onInputChange() {
|
||||
showError("");
|
||||
showError(QString());
|
||||
}
|
||||
|
||||
void IntroPwdCheck::onSubmitPwd(bool force) {
|
||||
|
@ -359,7 +363,7 @@ void IntroPwdCheck::onSubmitPwd(bool force) {
|
|||
_pwdField.setDisabled(true);
|
||||
setFocus();
|
||||
|
||||
showError("");
|
||||
showError(QString());
|
||||
|
||||
QByteArray pwdData = _salt + _pwdField.text().toUtf8() + _salt, pwdHash(32, Qt::Uninitialized);
|
||||
hashSha256(pwdData.constData(), pwdData.size(), pwdHash.data());
|
||||
|
@ -367,9 +371,6 @@ void IntroPwdCheck::onSubmitPwd(bool force) {
|
|||
}
|
||||
}
|
||||
|
||||
void IntroPwdCheck::onNext() {
|
||||
void IntroPwdCheck::onSubmit() {
|
||||
onSubmitPwd();
|
||||
}
|
||||
|
||||
void IntroPwdCheck::onBack() {
|
||||
}
|
||||
|
|
|
@ -23,24 +23,23 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include <QtWidgets/QWidget>
|
||||
#include "gui/flatbutton.h"
|
||||
#include "gui/flatinput.h"
|
||||
#include "intro.h"
|
||||
#include "intro/introwidget.h"
|
||||
|
||||
class IntroPwdCheck final : public IntroStage, public RPCSender {
|
||||
class IntroPwdCheck final : public IntroStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
IntroPwdCheck(IntroWidget *parent);
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void step_error(float64 ms, bool timer);
|
||||
|
||||
void activate();
|
||||
void deactivate();
|
||||
void onNext();
|
||||
void onBack();
|
||||
void activate() override;
|
||||
void cancelled() override;
|
||||
void onSubmit() override;
|
||||
|
||||
void pwdSubmitDone(bool recover, const MTPauth_Authorization &result);
|
||||
bool pwdSubmitFail(const RPCError &error);
|
||||
|
|
|
@ -28,9 +28,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "application.h"
|
||||
|
||||
#include "intro/introsignup.h"
|
||||
#include "intro/intro.h"
|
||||
|
||||
IntroSignup::IntroSignup(IntroWidget *parent) : IntroStage(parent)
|
||||
IntroSignup::IntroSignup(IntroWidget *parent) : IntroStep(parent)
|
||||
, a_errorAlpha(0)
|
||||
, a_photoOver(0)
|
||||
, _a_error(animation(this, &IntroSignup::step_error))
|
||||
|
@ -38,6 +37,7 @@ IntroSignup::IntroSignup(IntroWidget *parent) : IntroStage(parent)
|
|||
, next(this, lang(lng_intro_finish), st::btnIntroNext)
|
||||
, first(this, st::inpIntroName, lang(lng_signup_firstname))
|
||||
, last(this, st::inpIntroName, lang(lng_signup_lastname))
|
||||
, sentRequest(0)
|
||||
, _invertOrder(langFirstNameGoesSecond()) {
|
||||
setVisible(false);
|
||||
setGeometry(parent->innerRect());
|
||||
|
@ -180,7 +180,7 @@ void IntroSignup::step_error(float64 ms, bool timer) {
|
|||
_a_error.stop();
|
||||
a_errorAlpha.finish();
|
||||
if (!a_errorAlpha.current()) {
|
||||
error = "";
|
||||
error.clear();
|
||||
}
|
||||
} else {
|
||||
a_errorAlpha.update(dt, st::introErrFunc);
|
||||
|
@ -201,7 +201,7 @@ void IntroSignup::step_photo(float64 ms, bool timer) {
|
|||
}
|
||||
|
||||
void IntroSignup::activate() {
|
||||
show();
|
||||
IntroStep::activate();
|
||||
if (_invertOrder) {
|
||||
last.setFocus();
|
||||
} else {
|
||||
|
@ -209,8 +209,11 @@ void IntroSignup::activate() {
|
|||
}
|
||||
}
|
||||
|
||||
void IntroSignup::deactivate() {
|
||||
hide();
|
||||
void IntroSignup::cancelled() {
|
||||
if (sentRequest) {
|
||||
MTP::cancel(sentRequest);
|
||||
sentRequest = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void IntroSignup::stopCheck() {
|
||||
|
@ -264,7 +267,7 @@ bool IntroSignup::nameSubmitFail(const RPCError &error) {
|
|||
last.setDisabled(false);
|
||||
const QString &err = error.type();
|
||||
if (err == "PHONE_NUMBER_INVALID" || err == "PHONE_CODE_EXPIRED" || err == "PHONE_CODE_EMPTY" || err == "PHONE_CODE_INVALID" || err == "PHONE_NUMBER_OCCUPIED") {
|
||||
intro()->onIntroBack();
|
||||
intro()->onBack();
|
||||
return true;
|
||||
} else if (err == "FIRSTNAME_INVALID") {
|
||||
showError(lang(lng_bad_name));
|
||||
|
@ -297,7 +300,7 @@ bool IntroSignup::nameSubmitFail(const RPCError &error) {
|
|||
}
|
||||
|
||||
void IntroSignup::onInputChange() {
|
||||
showError("");
|
||||
showError(QString());
|
||||
}
|
||||
|
||||
void IntroSignup::onSubmitName(bool force) {
|
||||
|
@ -324,16 +327,13 @@ void IntroSignup::onSubmitName(bool force) {
|
|||
last.setDisabled(true);
|
||||
setFocus();
|
||||
|
||||
showError("");
|
||||
showError(QString());
|
||||
|
||||
firstName = first.text().trimmed();
|
||||
lastName = last.text().trimmed();
|
||||
sentRequest = MTP::send(MTPauth_SignUp(MTP_string(intro()->getPhone()), MTP_string(intro()->getPhoneHash()), MTP_string(intro()->getCode()), MTP_string(firstName), MTP_string(lastName)), rpcDone(&IntroSignup::nameSubmitDone), rpcFail(&IntroSignup::nameSubmitFail));
|
||||
}
|
||||
|
||||
void IntroSignup::onNext() {
|
||||
void IntroSignup::onSubmit() {
|
||||
onSubmitName();
|
||||
}
|
||||
|
||||
void IntroSignup::onBack() {
|
||||
}
|
||||
|
|
|
@ -23,27 +23,26 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include <QtWidgets/QWidget>
|
||||
#include "gui/flatbutton.h"
|
||||
#include "gui/flatinput.h"
|
||||
#include "intro.h"
|
||||
#include "intro/introwidget.h"
|
||||
|
||||
class IntroSignup : public IntroStage, public RPCSender {
|
||||
class IntroSignup final : public IntroStep {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
IntroSignup(IntroWidget *parent);
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void mouseMoveEvent(QMouseEvent *e);
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void mouseMoveEvent(QMouseEvent *e) override;
|
||||
void mousePressEvent(QMouseEvent *e) override;
|
||||
|
||||
void step_error(float64 ms, bool timer);
|
||||
void step_photo(float64 ms, bool timer);
|
||||
|
||||
void activate();
|
||||
void deactivate();
|
||||
void onNext();
|
||||
void onBack();
|
||||
void activate() override;
|
||||
void cancelled() override;
|
||||
void onSubmit() override;
|
||||
|
||||
void nameSubmitDone(const MTPauth_Authorization &result);
|
||||
bool nameSubmitFail(const RPCError &error);
|
||||
|
|
|
@ -24,16 +24,15 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "application.h"
|
||||
|
||||
#include "intro/introsteps.h"
|
||||
#include "intro/intro.h"
|
||||
#include "intro/introstart.h"
|
||||
#include "intro/introphone.h"
|
||||
|
||||
#include "langloaderplain.h"
|
||||
|
||||
IntroSteps::IntroSteps(IntroWidget *parent) : IntroStage(parent),
|
||||
_intro(this, lang(lng_intro), st::introLabel, st::introLabelTextStyle),
|
||||
_changeLang(this, QString()),
|
||||
_next(this, lang(lng_start_msgs), st::btnIntroNext) {
|
||||
|
||||
IntroStart::IntroStart(IntroWidget *parent) : IntroStep(parent)
|
||||
, _intro(this, lang(lng_intro), st::introLabel, st::introLabelTextStyle)
|
||||
, _changeLang(this, QString())
|
||||
, _next(this, lang(lng_start_msgs), st::btnIntroNext) {
|
||||
_changeLang.hide();
|
||||
if (cLang() == languageDefault) {
|
||||
int32 l = Sandbox::LangSystem();
|
||||
|
@ -56,15 +55,14 @@ _next(this, lang(lng_start_msgs), st::btnIntroNext) {
|
|||
|
||||
setGeometry(parent->innerRect());
|
||||
|
||||
connect(&_next, SIGNAL(stateChanged(int, ButtonStateChangeSource)), parent, SLOT(onDoneStateChanged(int, ButtonStateChangeSource)));
|
||||
connect(&_next, SIGNAL(clicked()), parent, SLOT(onIntroNext()));
|
||||
connect(&_next, SIGNAL(clicked()), parent, SLOT(onStepSubmit()));
|
||||
|
||||
connect(&_changeLang, SIGNAL(clicked()), parent, SLOT(onChangeLang()));
|
||||
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void IntroSteps::paintEvent(QPaintEvent *e) {
|
||||
void IntroStart::paintEvent(QPaintEvent *e) {
|
||||
bool trivial = (rect() == e->rect());
|
||||
|
||||
QPainter p(this);
|
||||
|
@ -80,7 +78,7 @@ void IntroSteps::paintEvent(QPaintEvent *e) {
|
|||
p.drawPixmap(QPoint((width() - st::aboutIcon.pxWidth()) / 2, hy - st::introIconSkip - st::aboutIcon.pxHeight()), App::sprite(), st::aboutIcon);
|
||||
}
|
||||
|
||||
void IntroSteps::resizeEvent(QResizeEvent *e) {
|
||||
void IntroStart::resizeEvent(QResizeEvent *e) {
|
||||
if (e->oldSize().width() != width()) {
|
||||
_next.move((width() - _next.width()) / 2, st::introBtnTop);
|
||||
_intro.move((width() - _intro.width()) / 2, _next.y() - _intro.height() - st::introSkip);
|
||||
|
@ -88,17 +86,6 @@ void IntroSteps::resizeEvent(QResizeEvent *e) {
|
|||
}
|
||||
}
|
||||
|
||||
void IntroSteps::activate() {
|
||||
show();
|
||||
}
|
||||
|
||||
void IntroSteps::deactivate() {
|
||||
hide();
|
||||
}
|
||||
|
||||
void IntroSteps::onNext() {
|
||||
intro()->onIntroNext();
|
||||
}
|
||||
|
||||
void IntroSteps::onBack() {
|
||||
void IntroStart::onSubmit() {
|
||||
intro()->nextStep(new IntroPhone(intro()));
|
||||
}
|
|
@ -20,21 +20,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "gui/flatbutton.h"
|
||||
#include "intro.h"
|
||||
#include "intro/introwidget.h"
|
||||
|
||||
class IntroSteps : public IntroStage {
|
||||
class IntroStart final : public IntroStep {
|
||||
public:
|
||||
|
||||
IntroSteps(IntroWidget *parent);
|
||||
IntroStart(IntroWidget *parent);
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
|
||||
void activate();
|
||||
void deactivate();
|
||||
void onNext();
|
||||
void onBack();
|
||||
void onSubmit() override;
|
||||
|
||||
private:
|
||||
|
|
@ -24,8 +24,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "localstorage.h"
|
||||
|
||||
#include "intro/intro.h"
|
||||
#include "intro/introsteps.h"
|
||||
#include "intro/introwidget.h"
|
||||
#include "intro/introstart.h"
|
||||
#include "intro/introphone.h"
|
||||
#include "intro/introcode.h"
|
||||
#include "intro/introsignup.h"
|
||||
|
@ -52,20 +52,13 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
IntroWidget::IntroWidget(Window *window) : TWidget(window)
|
||||
IntroWidget::IntroWidget(QWidget *parent) : TWidget(parent)
|
||||
, _langChangeTo(0)
|
||||
, _a_stage(animation(this, &IntroWidget::step_stage))
|
||||
, _cacheHideIndex(0)
|
||||
, _cacheShowIndex(0)
|
||||
, _a_show(animation(this, &IntroWidget::step_show))
|
||||
, steps(new IntroSteps(this))
|
||||
, phone(0)
|
||||
, code(0)
|
||||
, signup(0)
|
||||
, pwdcheck(0)
|
||||
, current(0)
|
||||
, moving(0)
|
||||
, _callTimeout(60)
|
||||
, _callStatus({ CallDisabled, 0 })
|
||||
, _registered(false)
|
||||
, _hasRecovery(false)
|
||||
, _codeByTelegram(false)
|
||||
|
@ -74,7 +67,7 @@ IntroWidget::IntroWidget(Window *window) : TWidget(window)
|
|||
, _backTo(0) {
|
||||
setGeometry(QRect(0, st::titleHeight, App::wnd()->width(), App::wnd()->height() - st::titleHeight));
|
||||
|
||||
connect(&_back, SIGNAL(clicked()), this, SLOT(onIntroBack()));
|
||||
connect(&_back, SIGNAL(clicked()), this, SLOT(onBack()));
|
||||
_back.hide();
|
||||
|
||||
countryForReg = psCurrentCountry();
|
||||
|
@ -82,11 +75,10 @@ IntroWidget::IntroWidget(Window *window) : TWidget(window)
|
|||
MTP::send(MTPhelp_GetNearestDc(), rpcDone(gotNearestDC));
|
||||
signalEmitOn = this;
|
||||
|
||||
stages[0] = steps;
|
||||
memset(stages + 1, 0, sizeof(QWidget*) * 3);
|
||||
_stepHistory.push_back(new IntroStart(this));
|
||||
_back.raise();
|
||||
|
||||
connect(window, SIGNAL(resized(const QSize&)), this, SLOT(onParentResize(const QSize&)));
|
||||
connect(parent, SIGNAL(resized(const QSize&)), this, SLOT(onParentResize(const QSize&)));
|
||||
|
||||
show();
|
||||
setFocus();
|
||||
|
@ -112,92 +104,77 @@ void IntroWidget::onParentResize(const QSize &newSize) {
|
|||
resize(newSize);
|
||||
}
|
||||
|
||||
void IntroWidget::onIntroBack() {
|
||||
if (!current) return;
|
||||
moving = (current == 4) ? -2 : -1;
|
||||
prepareMove();
|
||||
void IntroWidget::onStepSubmit() {
|
||||
step()->onSubmit();
|
||||
}
|
||||
|
||||
void IntroWidget::onIntroNext() {
|
||||
if (!createNext()) return;
|
||||
moving = 1;
|
||||
prepareMove();
|
||||
void IntroWidget::onBack() {
|
||||
historyMove(MoveBack);
|
||||
}
|
||||
|
||||
bool IntroWidget::createNext() {
|
||||
if (current == sizeof(stages) / sizeof(stages[0]) - 1) return false;
|
||||
if (!stages[current + 1]) {
|
||||
switch (current) {
|
||||
case 0: stages[current + 1] = phone = new IntroPhone(this); break;
|
||||
case 1: stages[current + 1] = code = new IntroCode(this); break;
|
||||
case 2:
|
||||
if (_pwdSalt.isEmpty()) {
|
||||
if (signup) delete signup;
|
||||
stages[current + 1] = signup = new IntroSignup(this);
|
||||
} else {
|
||||
stages[current + 1] = pwdcheck = new IntroPwdCheck(this);
|
||||
}
|
||||
break;
|
||||
case 3: stages[current + 1] = signup = new IntroSignup(this); break;
|
||||
}
|
||||
}
|
||||
_back.raise();
|
||||
return true;
|
||||
}
|
||||
void IntroWidget::historyMove(MoveType type) {
|
||||
if (_a_stage.animating()) return;
|
||||
|
||||
t_assert(_stepHistory.size() > 1);
|
||||
|
||||
void IntroWidget::prepareMove() {
|
||||
if (App::app()) App::app()->mtpPause();
|
||||
|
||||
if (_cacheHide.isNull() || _cacheHideIndex != current) makeHideCache();
|
||||
switch (type) {
|
||||
case MoveBack: {
|
||||
_cacheHide = grabStep();
|
||||
|
||||
stages[current + moving]->prepareShow();
|
||||
if (_cacheShow.isNull() || _cacheShowIndex != current + moving) makeShowCache();
|
||||
IntroStep *back = step();
|
||||
_backFrom = back->hasBack() ? 1 : 0;
|
||||
_stepHistory.pop_back();
|
||||
back->cancelled();
|
||||
delete back;
|
||||
} break;
|
||||
|
||||
int32 m = (moving > 0) ? 1 : -1;
|
||||
case MoveForward: {
|
||||
_cacheHide = grabStep(1);
|
||||
_backFrom = step(1)->hasBack() ? 1 : 0;
|
||||
step(1)->finished();
|
||||
} break;
|
||||
|
||||
case MoveReplace: {
|
||||
_cacheHide = grabStep(1);
|
||||
IntroStep *replaced = step(1);
|
||||
_backFrom = replaced->hasBack() ? 1 : 0;
|
||||
_stepHistory.removeAt(_stepHistory.size() - 2);
|
||||
replaced->finished();
|
||||
delete replaced;
|
||||
} break;
|
||||
}
|
||||
|
||||
_cacheShow = grabStep();
|
||||
_backTo = step()->hasBack() ? 1 : 0;
|
||||
|
||||
int32 m = (type == MoveBack) ? -1 : 1;
|
||||
a_coordHide = anim::ivalue(0, -m * st::introSlideShift);
|
||||
a_opacityHide = anim::fvalue(1, 0);
|
||||
a_coordShow = anim::ivalue(m * st::introSlideShift, 0);
|
||||
a_opacityShow = anim::fvalue(0, 1);
|
||||
_a_stage.start();
|
||||
|
||||
_backTo = stages[current + moving]->hasBack() ? 1 : 0;
|
||||
_backFrom = stages[current]->hasBack() ? 1 : 0;
|
||||
_a_stage.step();
|
||||
if (_backFrom > 0 || _backTo > 0) {
|
||||
_back.show();
|
||||
} else {
|
||||
_back.hide();
|
||||
}
|
||||
stages[current]->deactivate();
|
||||
stages[current + moving]->hide();
|
||||
step()->hide();
|
||||
}
|
||||
|
||||
void IntroWidget::onDoneStateChanged(int oldState, ButtonStateChangeSource source) {
|
||||
if (_a_stage.animating()) return;
|
||||
if (source == ButtonByPress) {
|
||||
if (oldState & Button::StateDown) {
|
||||
_cacheHide = QPixmap();
|
||||
} else {
|
||||
makeHideCache();
|
||||
}
|
||||
} else if (source == ButtonByHover && current != 2) {
|
||||
if (!createNext()) return;
|
||||
if (!_cacheShow) makeShowCache(current + 1);
|
||||
}
|
||||
void IntroWidget::pushStep(IntroStep *step, MoveType type) {
|
||||
_stepHistory.push_back(step);
|
||||
_back.raise();
|
||||
_stepHistory.back()->hide();
|
||||
|
||||
historyMove(type);
|
||||
}
|
||||
|
||||
void IntroWidget::makeHideCache(int stage) {
|
||||
if (stage < 0) stage = current;
|
||||
int w = st::introSize.width(), h = st::introSize.height();
|
||||
_cacheHide = myGrab(stages[stage], QRect(st::introSlideShift, 0, w, h));
|
||||
_cacheHideIndex = stage;
|
||||
}
|
||||
|
||||
void IntroWidget::makeShowCache(int stage) {
|
||||
if (stage < 0) stage = current + moving;
|
||||
int w = st::introSize.width(), h = st::introSize.height();
|
||||
_cacheShow = myGrab(stages[stage], QRect(st::introSlideShift, 0, w, h));
|
||||
_cacheShowIndex = stage;
|
||||
QPixmap IntroWidget::grabStep(int skip) {
|
||||
return myGrab(step(skip), QRect(st::introSlideShift, 0, st::introSize.width(), st::introSize.height()));
|
||||
}
|
||||
|
||||
void IntroWidget::animShow(const QPixmap &bgAnimCache, bool back) {
|
||||
|
@ -206,8 +183,8 @@ void IntroWidget::animShow(const QPixmap &bgAnimCache, bool back) {
|
|||
(back ? _cacheOver : _cacheUnder) = bgAnimCache;
|
||||
|
||||
_a_show.stop();
|
||||
stages[current]->show();
|
||||
if (stages[current]->hasBack()) {
|
||||
step()->show();
|
||||
if (step()->hasBack()) {
|
||||
_back.setOpacity(1);
|
||||
_back.show();
|
||||
} else {
|
||||
|
@ -215,8 +192,7 @@ void IntroWidget::animShow(const QPixmap &bgAnimCache, bool back) {
|
|||
}
|
||||
(back ? _cacheUnder : _cacheOver) = myGrab(this);
|
||||
|
||||
stages[current]->deactivate();
|
||||
stages[current]->hide();
|
||||
step()->hide();
|
||||
_back.hide();
|
||||
|
||||
a_coordUnder = back ? anim::ivalue(-qFloor(st::slideShift * width()), 0) : anim::ivalue(0, -qFloor(st::slideShift * width()));
|
||||
|
@ -239,9 +215,8 @@ void IntroWidget::step_show(float64 ms, bool timer) {
|
|||
_cacheUnder = _cacheOver = QPixmap();
|
||||
|
||||
setFocus();
|
||||
stages[current]->show();
|
||||
stages[current]->activate();
|
||||
if (stages[current]->hasBack()) {
|
||||
step()->activate();
|
||||
if (step()->hasBack()) {
|
||||
_back.setOpacity(1);
|
||||
_back.show();
|
||||
}
|
||||
|
@ -269,11 +244,9 @@ void IntroWidget::step_stage(float64 ms, bool timer) {
|
|||
|
||||
_cacheHide = _cacheShow = QPixmap();
|
||||
|
||||
current += moving;
|
||||
moving = 0;
|
||||
setFocus();
|
||||
stages[current]->activate();
|
||||
if (!stages[current]->hasBack()) {
|
||||
step()->activate();
|
||||
if (!step()->hasBack()) {
|
||||
_back.hide();
|
||||
}
|
||||
if (App::app()) App::app()->mtpUnpause();
|
||||
|
@ -312,9 +285,9 @@ void IntroWidget::paintEvent(QPaintEvent *e) {
|
|||
p.drawPixmap(QRect(a_coordOver.current() - st::slideShadow.pxWidth(), 0, st::slideShadow.pxWidth(), height()), App::sprite(), st::slideShadow);
|
||||
} else if (_a_stage.animating()) {
|
||||
p.setOpacity(a_opacityHide.current());
|
||||
p.drawPixmap(stages[current]->x() + st::introSlideShift + a_coordHide.current(), stages[current]->y(), _cacheHide);
|
||||
p.drawPixmap(step()->x() + st::introSlideShift + a_coordHide.current(), step()->y(), _cacheHide);
|
||||
p.setOpacity(a_opacityShow.current());
|
||||
p.drawPixmap(stages[current + moving]->x() + st::introSlideShift + a_coordShow.current(), stages[current + moving]->y(), _cacheShow);
|
||||
p.drawPixmap(step()->x() + st::introSlideShift + a_coordShow.current(), step()->y(), _cacheShow);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,11 +312,6 @@ void IntroWidget::setCode(const QString &code) {
|
|||
|
||||
void IntroWidget::setPwdSalt(const QByteArray &salt) {
|
||||
_pwdSalt = salt;
|
||||
delete signup;
|
||||
delete pwdcheck;
|
||||
stages[3] = stages[4] = 0;
|
||||
signup = 0;
|
||||
pwdcheck = 0;
|
||||
}
|
||||
|
||||
void IntroWidget::setHasRecovery(bool has) {
|
||||
|
@ -356,11 +324,10 @@ void IntroWidget::setPwdHint(const QString &hint) {
|
|||
|
||||
void IntroWidget::setCodeByTelegram(bool byTelegram) {
|
||||
_codeByTelegram = byTelegram;
|
||||
if (code) code->updateDescText();
|
||||
}
|
||||
|
||||
void IntroWidget::setCallTimeout(int32 callTimeout) {
|
||||
_callTimeout = callTimeout;
|
||||
void IntroWidget::setCallStatus(const CallStatus &status) {
|
||||
_callStatus = status;
|
||||
}
|
||||
|
||||
const QString &IntroWidget::getPhone() const {
|
||||
|
@ -375,8 +342,8 @@ const QString &IntroWidget::getCode() const {
|
|||
return _code;
|
||||
}
|
||||
|
||||
int32 IntroWidget::getCallTimeout() const {
|
||||
return _callTimeout;
|
||||
const IntroWidget::CallStatus &IntroWidget::getCallStatus() const {
|
||||
return _callStatus;
|
||||
}
|
||||
|
||||
const QByteArray &IntroWidget::getPwdSalt() const {
|
||||
|
@ -397,15 +364,9 @@ bool IntroWidget::codeByTelegram() const {
|
|||
|
||||
void IntroWidget::resizeEvent(QResizeEvent *e) {
|
||||
QRect r(innerRect());
|
||||
if (steps) steps->setGeometry(r);
|
||||
if (phone) phone->setGeometry(r);
|
||||
if (code) code->setGeometry(r);
|
||||
if (signup) signup->setGeometry(r);
|
||||
if (pwdcheck) pwdcheck->setGeometry(r);
|
||||
}
|
||||
|
||||
void IntroWidget::mousePressEvent(QMouseEvent *e) {
|
||||
|
||||
for (IntroStep *step : _stepHistory) {
|
||||
step->setGeometry(r);
|
||||
}
|
||||
}
|
||||
|
||||
void IntroWidget::finish(const MTPUser &user, const QImage &photo) {
|
||||
|
@ -419,9 +380,11 @@ void IntroWidget::keyPressEvent(QKeyEvent *e) {
|
|||
if (_a_show.animating() || _a_stage.animating()) return;
|
||||
|
||||
if (e->key() == Qt::Key_Escape) {
|
||||
stages[current]->onBack();
|
||||
if (step()->hasBack()) {
|
||||
onBack();
|
||||
}
|
||||
} else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) {
|
||||
stages[current]->onNext();
|
||||
onStepSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -429,17 +392,16 @@ void IntroWidget::updateAdaptiveLayout() {
|
|||
}
|
||||
|
||||
void IntroWidget::rpcClear() {
|
||||
if (phone) phone->rpcClear();
|
||||
if (code) code->rpcClear();
|
||||
if (signup) signup->rpcClear();
|
||||
if (pwdcheck) pwdcheck->rpcClear();
|
||||
for (IntroStep *step : _stepHistory) {
|
||||
step->rpcClear();
|
||||
}
|
||||
}
|
||||
|
||||
IntroWidget::~IntroWidget() {
|
||||
delete steps;
|
||||
delete phone;
|
||||
delete code;
|
||||
delete signup;
|
||||
delete pwdcheck;
|
||||
while (!_stepHistory.isEmpty()) {
|
||||
IntroStep *back = _stepHistory.back();
|
||||
_stepHistory.pop_back();
|
||||
delete back;
|
||||
}
|
||||
if (App::wnd()) App::wnd()->noIntro(this);
|
||||
}
|
|
@ -20,26 +20,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
class Window;
|
||||
class IntroSteps;
|
||||
class IntroPhone;
|
||||
class IntroCode;
|
||||
class IntroSignup;
|
||||
class IntroPwdCheck;
|
||||
class IntroStage;
|
||||
class Text;
|
||||
|
||||
class IntroStep;
|
||||
class IntroWidget final : public TWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
IntroWidget(Window *window);
|
||||
IntroWidget(QWidget *window);
|
||||
|
||||
void paintEvent(QPaintEvent *e);
|
||||
void resizeEvent(QResizeEvent *e);
|
||||
void mousePressEvent(QMouseEvent *e);
|
||||
void keyPressEvent(QKeyEvent *e);
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *e) override;
|
||||
void keyPressEvent(QKeyEvent *e) override;
|
||||
|
||||
void updateAdaptiveLayout();
|
||||
|
||||
|
@ -52,9 +43,19 @@ public:
|
|||
QRect innerRect() const;
|
||||
QString currentCountry() const;
|
||||
|
||||
enum CallStatusType {
|
||||
CallWaiting,
|
||||
CallCalling,
|
||||
CallCalled,
|
||||
CallDisabled,
|
||||
};
|
||||
struct CallStatus {
|
||||
CallStatusType type;
|
||||
int timeout;
|
||||
};
|
||||
void setPhone(const QString &phone, const QString &phone_hash, bool registered);
|
||||
void setCode(const QString &code);
|
||||
void setCallTimeout(int32 callTimeout);
|
||||
void setCallStatus(const CallStatus &status);
|
||||
void setPwdSalt(const QByteArray &salt);
|
||||
void setHasRecovery(bool hasRecovery);
|
||||
void setPwdHint(const QString &hint);
|
||||
|
@ -63,7 +64,7 @@ public:
|
|||
const QString &getPhone() const;
|
||||
const QString &getPhoneHash() const;
|
||||
const QString &getCode() const;
|
||||
int32 getCallTimeout() const;
|
||||
const CallStatus &getCallStatus() const;
|
||||
const QByteArray &getPwdSalt() const;
|
||||
bool getHasRecovery() const;
|
||||
const QString &getPwdHint() const;
|
||||
|
@ -74,13 +75,19 @@ public:
|
|||
void rpcClear();
|
||||
void langChangeTo(int32 langId);
|
||||
|
||||
~IntroWidget();
|
||||
void nextStep(IntroStep *step) {
|
||||
pushStep(step, MoveForward);
|
||||
}
|
||||
void replaceStep(IntroStep *step) {
|
||||
pushStep(step, MoveReplace);
|
||||
}
|
||||
|
||||
~IntroWidget() override;
|
||||
|
||||
public slots:
|
||||
|
||||
void onIntroNext();
|
||||
void onIntroBack();
|
||||
void onDoneStateChanged(int oldState, ButtonStateChangeSource source);
|
||||
void onStepSubmit();
|
||||
void onBack();
|
||||
void onParentResize(const QSize &newSize);
|
||||
void onChangeLang();
|
||||
|
||||
|
@ -90,12 +97,9 @@ signals:
|
|||
|
||||
private:
|
||||
|
||||
void makeHideCache(int stage = -1);
|
||||
void makeShowCache(int stage = -1);
|
||||
void prepareMove();
|
||||
bool createNext();
|
||||
QPixmap grabStep(int skip = 0);
|
||||
|
||||
int32 _langChangeTo;
|
||||
int _langChangeTo;
|
||||
|
||||
Animation _a_stage;
|
||||
QPixmap _cacheHide, _cacheShow;
|
||||
|
@ -108,16 +112,21 @@ private:
|
|||
anim::ivalue a_coordUnder, a_coordOver;
|
||||
anim::fvalue a_shadow;
|
||||
|
||||
IntroSteps *steps;
|
||||
IntroPhone *phone;
|
||||
IntroCode *code;
|
||||
IntroSignup *signup;
|
||||
IntroPwdCheck *pwdcheck;
|
||||
IntroStage *stages[5];
|
||||
int current, moving;
|
||||
QVector<IntroStep*> _stepHistory;
|
||||
IntroStep *step(int skip = 0) {
|
||||
t_assert(_stepHistory.size() + skip > 0);
|
||||
return _stepHistory.at(_stepHistory.size() - skip - 1);
|
||||
}
|
||||
enum MoveType {
|
||||
MoveBack,
|
||||
MoveForward,
|
||||
MoveReplace,
|
||||
};
|
||||
void historyMove(MoveType type);
|
||||
void pushStep(IntroStep *step, MoveType type);
|
||||
|
||||
QString _phone, _phone_hash;
|
||||
int32 _callTimeout;
|
||||
CallStatus _callStatus;
|
||||
bool _registered;
|
||||
|
||||
QString _code;
|
||||
|
@ -133,26 +142,31 @@ private:
|
|||
|
||||
};
|
||||
|
||||
class IntroStage : public TWidget {
|
||||
class IntroStep : public TWidget, public RPCSender {
|
||||
public:
|
||||
|
||||
IntroStage(IntroWidget *parent) : TWidget(parent) {
|
||||
IntroStep(IntroWidget *parent) : TWidget(parent) {
|
||||
}
|
||||
|
||||
virtual void activate() = 0; // show and activate
|
||||
virtual void prepareShow() {
|
||||
}
|
||||
virtual void deactivate() = 0; // deactivate and hide
|
||||
virtual void onNext() = 0;
|
||||
virtual void onBack() = 0;
|
||||
virtual bool hasBack() const {
|
||||
return false;
|
||||
}
|
||||
virtual void activate() {
|
||||
show();
|
||||
}
|
||||
virtual void cancelled() {
|
||||
}
|
||||
virtual void finished() {
|
||||
hide();
|
||||
}
|
||||
virtual void onSubmit() = 0;
|
||||
|
||||
protected:
|
||||
|
||||
IntroWidget *intro() {
|
||||
return qobject_cast<IntroWidget*>(parent());
|
||||
IntroWidget *result = qobject_cast<IntroWidget*>(parentWidget());
|
||||
t_assert(result != nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
};
|
|
@ -87,7 +87,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_continue" = "Weiter";
|
||||
"lng_close" = "Schließen";
|
||||
"lng_connecting" = "Verbinde...";
|
||||
"lng_reconnecting" = "Neu verbinden {count:jetzt|in # s|in # s}..";
|
||||
"lng_reconnecting" = "Neu verbinden {count:jetzt|in # s|in # s}...";
|
||||
"lng_reconnecting_try_now" = "Jetzt versuchen";
|
||||
|
||||
"lng_status_service_notifications" = "Servicemeldungen";
|
||||
|
@ -108,7 +108,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_status_lastseen_date" = "zuletzt gesehen am {date}";
|
||||
"lng_status_lastseen_date_time" = "zuletzt gesehen am {date} um {time}";
|
||||
"lng_status_online" = "online";
|
||||
"lng_status_connecting" = "verbinden..";
|
||||
"lng_status_connecting" = "verbinden...";
|
||||
|
||||
"lng_chat_status_unaccessible" = "Gruppe ist nicht verfügbar";
|
||||
"lng_chat_status_members" = "{count:keine Mitglieder|# Mitglied|# Mitglieder}";
|
||||
|
@ -127,7 +127,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_edit_deleted" = "Diese Nachricht wurde gelöscht";
|
||||
"lng_edit_too_long" = "Der Text ist leider zu lang";
|
||||
"lng_edit_message" = "Nachricht bearbeiten";
|
||||
"lng_edit_message_text" = "Neuer Text..";
|
||||
"lng_edit_message_text" = "Neuer Text...";
|
||||
"lng_deleted" = "Gelöschter Kontakt";
|
||||
"lng_deleted_message" = "Gelöschte Nachricht";
|
||||
"lng_pinned_message" = "Angeheftete Nachricht";
|
||||
|
@ -162,7 +162,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_code_telegram" = "Bitte den Code eingeben, den du in der\nzuvor benutzen [b]Telegram[/b]-App erhalten hast.";
|
||||
"lng_code_no_telegram" = "Code per SMS senden";
|
||||
"lng_code_call" = "Telegram ruft dich an in {minutes}:{seconds}";
|
||||
"lng_code_calling" = "Telegram ruft dich an..";
|
||||
"lng_code_calling" = "Telegram ruft dich an...";
|
||||
"lng_code_called" = "Telegram ruft dich gerade an.";
|
||||
|
||||
"lng_bad_phone" = "Falsche Nummer, bitte erneut versuchen.";
|
||||
|
@ -201,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_dlg_new_channel_name" = "Kanalname";
|
||||
"lng_no_contacts" = "Du hast keine Kontakte";
|
||||
"lng_no_chats" = "Noch keine Chats";
|
||||
"lng_contacts_loading" = "Lade..";
|
||||
"lng_contacts_loading" = "Lade...";
|
||||
"lng_contacts_not_found" = "Keine Kontakte gefunden";
|
||||
"lng_dlg_search_chat" = "In diesem Chat suchen";
|
||||
"lng_dlg_search_channel" = "In diesem Kanal suchen";
|
||||
|
@ -210,7 +210,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_save" = "Speichern";
|
||||
"lng_settings_upload" = "Profilbild festlegen";
|
||||
"lng_settings_crop_profile" = "Sichtbaren Bereich für Bild wählen";
|
||||
"lng_settings_uploading_photo" = "Bild wird geladen..";
|
||||
"lng_settings_uploading_photo" = "Bild wird geladen...";
|
||||
|
||||
"lng_username_title" = "Benutzername";
|
||||
"lng_username_about" = "Wähle einen (optionalen) öffentlichen \nBenutzernamen, wenn du von anderen \ngefunden werden willst, ohne, dass sie \ndeine Nummer kennen müssen.\n\nErlaubt sind a-z, 0-9 und Unterstriche. \nDie Mindestlänge beträgt 5 Zeichen.";
|
||||
|
@ -247,9 +247,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_auto_update" = "Auto-Updates";
|
||||
"lng_settings_current_version" = "Version {version}";
|
||||
"lng_settings_check_now" = "Auf Updates prüfen";
|
||||
"lng_settings_update_checking" = "Prüfe auf Updates..";
|
||||
"lng_settings_update_checking" = "Prüfe auf Updates...";
|
||||
"lng_settings_latest_installed" = "Aktuellste Version bereits installiert";
|
||||
"lng_settings_downloading" = "Update wird geladen {ready} / {total} MB..";
|
||||
"lng_settings_downloading" = "Update wird geladen {ready} / {total} MB...";
|
||||
"lng_settings_update_ready" = "Neue Version kann installiert werden";
|
||||
"lng_settings_update_now" = "Jetzt neustarten";
|
||||
"lng_settings_update_fail" = "Konnte nicht auf Updates prüfen :(";
|
||||
|
@ -291,7 +291,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_download_path_failed" = "Download konnte nicht gestartet werden. Das kann am eingestellten Speicherort liegen.\n\nDu kannst den Speicherort in den Einstellungen ändern.";
|
||||
"lng_download_path_settings" = "Einstellungen";
|
||||
"lng_download_finish_failed" = "Datei konnte nicht geladen werden.\n\nErneut versuchen?";
|
||||
"lng_download_path_clearing" = "Leeren..";
|
||||
"lng_download_path_clearing" = "Leeren...";
|
||||
"lng_download_path_cleared" = "Geleert!";
|
||||
"lng_download_path_clear_failed" = "Ein Fehler ist aufgetreten :(";
|
||||
|
||||
|
@ -300,7 +300,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_images_cached" = "{count:_not_used_|# Bild|# Bilder}, {size}";
|
||||
"lng_settings_audios_cached" = "{count:_not_used_|# Sprachnachricht|# Sprachnachrichten}, {size}";
|
||||
"lng_local_storage_clear" = "Leeren";
|
||||
"lng_local_storage_clearing" = "Entferne..";
|
||||
"lng_local_storage_clearing" = "Leeren...";
|
||||
"lng_local_storage_cleared" = "Alles entfernt!";
|
||||
"lng_local_storage_clear_failed" = "Ein Fehler ist aufgetreten :(";
|
||||
|
||||
|
@ -331,7 +331,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_passcode_logout" = "Abmelden";
|
||||
"lng_passcode_need_unblock" = "Bitte erst entsperren.";
|
||||
|
||||
"lng_cloud_password_waiting" = "Bestätigungslink gesendet an {email}..";
|
||||
"lng_cloud_password_waiting" = "Bestätigungslink gesendet an {email}...";
|
||||
"lng_cloud_password_change" = "Kennwort ändern";
|
||||
"lng_cloud_password_create" = "Kennwort erstellen";
|
||||
"lng_cloud_password_remove" = "Kennwort entfernen";
|
||||
|
@ -358,7 +358,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cloud_password_is_same" = "Kennwort wurde nicht geändert";
|
||||
|
||||
"lng_connection_type" = "Verbindungsart:";
|
||||
"lng_connection_auto_connecting" = "Standard (verbinden..)";
|
||||
"lng_connection_auto_connecting" = "Standard (verbinden...)";
|
||||
"lng_connection_auto" = "Standard ({transport} verwendet)";
|
||||
"lng_connection_proxy_connecting" = "Verbinde über Proxy...";
|
||||
"lng_connection_proxy" = "{transport} mit Proxy";
|
||||
|
@ -439,7 +439,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_sure_kick" = "{user} aus der Gruppe entfernen?";
|
||||
"lng_profile_sure_kick_channel" = "{user} aus dem Kanal entfernen?";
|
||||
"lng_profile_sure_kick_admin" = "{user} als Administrator entfernen?";
|
||||
"lng_profile_loading" = "Lade..";
|
||||
"lng_profile_loading" = "Lade...";
|
||||
"lng_profile_shared_media" = "Geteilte Medien";
|
||||
"lng_profile_no_media" = "Noch keine Medien in diesem Chat";
|
||||
"lng_profile_photos" = "{count:_not_used_|# Bild|# Bilder} »";
|
||||
|
@ -455,6 +455,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_shared_links" = "{count:_not_used_|# Link|# Links} »";
|
||||
"lng_profile_shared_links_header" = "Links aus dem Chat";
|
||||
"lng_profile_copy_phone" = "Telefonnummer kopieren";
|
||||
"lng_profile_copy_fullname" = "Anzeigename kopieren";
|
||||
|
||||
"lng_channel_add_admins" = "Neuer Administrator";
|
||||
"lng_channel_add_members" = "Mitglieder hinzufügen";
|
||||
|
@ -659,10 +660,11 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_remove" = "Löschen";
|
||||
"lng_stickers_return" = "Rückgängig";
|
||||
"lng_stickers_restore" = "Zeigen";
|
||||
"lng_stickers_count" = "{count:Lade..|# Sticker|# Sticker}";
|
||||
"lng_stickers_count" = "{count:Lade...|# Sticker|# Sticker}";
|
||||
|
||||
"lng_in_dlg_photo" = "Bild";
|
||||
"lng_in_dlg_video" = "Video";
|
||||
"lng_in_dlg_video" = "Videodatei";
|
||||
"lng_in_dlg_audio_file" = "Audiodatei";
|
||||
"lng_in_dlg_contact" = "Kontakt";
|
||||
"lng_in_dlg_audio" = "Sprachnachricht";
|
||||
"lng_in_dlg_file" = "Datei";
|
||||
|
@ -678,18 +680,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_report_spam_sure_group" = "Spam in dieser Gruppe melden?";
|
||||
"lng_report_spam_sure_channel" = "Möchtest du wirklich Spam in diesem Kanal melden?";
|
||||
"lng_report_spam_ok" = "Melden";
|
||||
"lng_cant_send_to_not_contact" = "Derzeit kannst du nur Personen schreiben, wenn ihr eure Nummern ausgetauscht habt. {more_info}";
|
||||
"lng_cant_invite_not_contact" = "Du kannst nur Personen hinzufügen, wenn ihr eure Nummern ausgetauscht habt. {more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Du kannst nur Personen hinzufügen,wenn ihr\neure Nummern ausgetauscht habt. {more_info}\n";
|
||||
"lng_cant_send_to_not_contact" = "Derzeit kannst du nur Personen schreiben,\nwenn ihr eure Nummern ausgetauscht habt.\n{more_info}";
|
||||
"lng_cant_invite_not_contact" = "Du kannst nur Personen hinzufügen,\nwenn ihr eure Nummern ausgetauscht habt.\n{more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Du kannst nur Personen hinzufügen,\nwenn ihr eure Nummern ausgetauscht habt.\n{more_info}";
|
||||
"lng_cant_more_info" = "Weitere Infos »";
|
||||
"lng_cant_invite_banned" = "Nur Admins können diesen Nutzer hinzufügen.";
|
||||
"lng_cant_invite_privacy" = "Du kannst mit diesen Nutzern keine Gruppe erstellen, weil sie es nicht erlauben.";
|
||||
"lng_cant_invite_privacy_channel" = "Du kannst diese Nutzer keinen Kanälen hinzufügen, weil sie es nicht erlauben.";
|
||||
"lng_cant_do_this" = "Verzeihung. Das ist leider nicht möglich.";
|
||||
|
||||
"lng_send_button" = "Senden";
|
||||
"lng_message_ph" = "Schreibe deine Nachricht..";
|
||||
"lng_comment_ph" = "Schreibe ein Kommentar..";
|
||||
"lng_broadcast_ph" = "Sende einen Broadcast..";
|
||||
"lng_broadcast_silent_ph" = "Lautloser Broadcast";
|
||||
"lng_message_ph" = "Schreibe deine Nachricht...";
|
||||
"lng_comment_ph" = "Schreibe ein Kommentar...";
|
||||
"lng_broadcast_ph" = "Sende einen Broadcast...";
|
||||
"lng_broadcast_silent_ph" = "Lautloser Broadcast...";
|
||||
"lng_record_cancel" = "Zum Abbrechen rausbewegen";
|
||||
"lng_will_be_notified" = "Mitglieder werden benachrichtigt";
|
||||
"lng_wont_be_notified" = "Mitglieder werden nicht benachrichtigt";
|
||||
|
@ -718,26 +722,27 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_users_typing" = "{user} und {second_user} tippen";
|
||||
"lng_many_typing" = "{count:_not_used_|# tippt|# tippen}";
|
||||
"lng_send_action_record_video" = "schickt Video";
|
||||
"lng_user_action_record_video" = "{user} sendet Video";
|
||||
"lng_send_action_upload_video" = "schickt Video";
|
||||
"lng_user_action_upload_video" = "{user} sendet Video";
|
||||
"lng_send_action_record_audio" = "nimmt Audio auf";
|
||||
"lng_user_action_record_audio" = "{user} nimmt Audio auf";
|
||||
"lng_send_action_upload_audio" = "nimmt Audio auf";
|
||||
"lng_user_action_upload_audio" = "{user} sendet Audio";
|
||||
"lng_send_action_upload_photo" = "sendet Bild";
|
||||
"lng_user_action_upload_photo" = "{user} sendet Bild";
|
||||
"lng_send_action_upload_file" = "sendet Datei";
|
||||
"lng_user_action_upload_file" = "{user} sendet Datei";
|
||||
"lng_send_action_geo_location" = "wählt Standort aus";
|
||||
"lng_user_action_geo_location" = "{user} wählt Standort aus";
|
||||
"lng_send_action_choose_contact" = "wählt Kontakt aus";
|
||||
"lng_user_action_choose_contact" = "{user} wählt Kontakt aus";
|
||||
"lng_user_action_record_video" = "{user} sendet ein Video";
|
||||
"lng_send_action_upload_video" = "schickt ein Video";
|
||||
"lng_user_action_upload_video" = "{user} sendet ein Video";
|
||||
"lng_send_action_record_audio" = "nimmt ein Audio auf";
|
||||
"lng_user_action_record_audio" = "{user} nimmt eine Sprachnachricht auf";
|
||||
"lng_send_action_upload_audio" = "sendet eine Sprachnachricht";
|
||||
"lng_user_action_upload_audio" = "{user} sendet eine Sprachnachricht";
|
||||
"lng_send_action_upload_photo" = "sendet ein Bild";
|
||||
"lng_user_action_upload_photo" = "{user} sendet ein Bild";
|
||||
"lng_send_action_upload_file" = "sendet eine Datei";
|
||||
"lng_user_action_upload_file" = "{user} sendet eine Datei";
|
||||
"lng_send_action_geo_location" = "wählt einen Standort aus";
|
||||
"lng_user_action_geo_location" = "{user} wählt einen Standort aus";
|
||||
"lng_send_action_choose_contact" = "wählt einen Kontakt aus";
|
||||
"lng_user_action_choose_contact" = "{user} wählt einen Kontakt aus";
|
||||
"lng_unread_bar" = "{count:_not_used_|# Ungelesene Nachricht|# Ungelesene Nachrichten}";
|
||||
|
||||
"lng_maps_point" = "Standort";
|
||||
"lng_save_photo" = "Bild speichern";
|
||||
"lng_save_video" = "Video speichern";
|
||||
"lng_save_video" = "Videodatei speichern";
|
||||
"lng_save_audio_file" = "Audiodatei speichern";
|
||||
"lng_save_audio" = "Sprachnachricht speichern";
|
||||
"lng_save_file" = "Datei speichern";
|
||||
"lng_save_downloaded" = "{ready} / {total} {mb}";
|
||||
|
@ -755,7 +760,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_copy_email" = "E-Mail-Adresse kopieren";
|
||||
"lng_context_copy_hashtag" = "Hashtag kopieren";
|
||||
"lng_context_copy_mention" = "Benutzername kopieren";
|
||||
"lng_context_save_image" = "Bild speichern unter";
|
||||
"lng_context_save_image" = "Bild speichern unter...";
|
||||
"lng_context_forward_image" = "Bild weiterleiten";
|
||||
"lng_context_delete_image" = "Bild löschen";
|
||||
"lng_context_copy_image" = "Bild kopieren";
|
||||
|
@ -763,11 +768,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_cancel_download" = "Download abbrechen";
|
||||
"lng_context_show_in_folder" = "Im Ordner anzeigen";
|
||||
"lng_context_show_in_finder" = "Im Finder zeigen";
|
||||
"lng_context_save_video" = "Video speichern unter..";
|
||||
"lng_context_save_audio" = "Sprachnachricht speichern unter..";
|
||||
"lng_context_save_video" = "Videodatei speichern unter...";
|
||||
"lng_context_save_audio_file" = "Audiodatei speichern unter...";
|
||||
"lng_context_save_audio" = "Sprachnachricht speichern unter...";
|
||||
"lng_context_pack_info" = "Sticker-Paket";
|
||||
"lng_context_pack_add" = "Sticker hinzufügen";
|
||||
"lng_context_save_file" = "Datei speichern als..";
|
||||
"lng_context_save_file" = "Datei speichern unter...";
|
||||
"lng_context_forward_file" = "Datei weiterleiten";
|
||||
"lng_context_delete_file" = "Datei löschen";
|
||||
"lng_context_close_file" = "Datei schließen";
|
||||
|
@ -794,7 +800,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_send_image_too_large" = "Nur Dateien bis maximal 1,5 GB können gesendet werden :(";
|
||||
"lng_send_folder" = "Verzeichnis «{name}» kann nicht gesendet werden :(";
|
||||
|
||||
"lng_forward_choose" = "Empfänger wählen..";
|
||||
"lng_forward_choose" = "Empfänger wählen...";
|
||||
"lng_forward_cant" = "Weiterleiten nicht möglich :(";
|
||||
"lng_forward_confirm" = "An {recipient} weiterleiten?";
|
||||
"lng_forward_share_contact" = "Kontakt an {recipient} senden?";
|
||||
|
|
|
@ -127,7 +127,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_edit_deleted" = "Este mensaje fue eliminado";
|
||||
"lng_edit_too_long" = "Tu texto es demasiado largo";
|
||||
"lng_edit_message" = "Editar mensaje";
|
||||
"lng_edit_message_text" = "Nuevo texto...";
|
||||
"lng_edit_message_text" = "Nuevo mensaje...";
|
||||
"lng_deleted" = "Desconocido";
|
||||
"lng_deleted_message" = "Mensaje eliminado";
|
||||
"lng_pinned_message" = "Mensaje anclado";
|
||||
|
@ -210,7 +210,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_save" = "Guardar";
|
||||
"lng_settings_upload" = "Poner foto de perfil";
|
||||
"lng_settings_crop_profile" = "Selecciona el área para tu foto de perfil";
|
||||
"lng_settings_uploading_photo" = "Cargando foto...";
|
||||
"lng_settings_uploading_photo" = "Subiendo foto...";
|
||||
|
||||
"lng_username_title" = "Alias";
|
||||
"lng_username_about" = "Puedes elegir un alias en Telegram. \nSi lo haces, otras personas te podrán \nencontrar por ese alias y contactarte \nsin saber tu número de teléfono.\n\nPuedes usar a-z, 0-9 y guiones bajos.\nLa longitud mínima es de 5 caracteres.";
|
||||
|
@ -360,7 +360,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_connection_type" = "Tipo de conexión:";
|
||||
"lng_connection_auto_connecting" = "Por defecto (conectando...)";
|
||||
"lng_connection_auto" = "Por defecto ({transport} en uso)";
|
||||
"lng_connection_proxy_connecting" = "Conectando a través de un proxy...";
|
||||
"lng_connection_proxy_connecting" = "Conectando a través de proxy...";
|
||||
"lng_connection_proxy" = "{transport} con un proxy";
|
||||
"lng_connection_header" = "Tipo de conexión";
|
||||
"lng_connection_auto_rb" = "Automático (TCP si existe o HTTP)";
|
||||
|
@ -455,6 +455,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_shared_links" = "{count:_not_used_|# enlace|# enlaces} »";
|
||||
"lng_profile_shared_links_header" = "Enlaces";
|
||||
"lng_profile_copy_phone" = "Copiar número";
|
||||
"lng_profile_copy_fullname" = "Copiar nombre";
|
||||
|
||||
"lng_channel_add_admins" = "Nuevo administrador";
|
||||
"lng_channel_add_members" = "Añadir miembros";
|
||||
|
@ -663,8 +664,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
"lng_in_dlg_photo" = "Foto";
|
||||
"lng_in_dlg_video" = "Vídeo";
|
||||
"lng_in_dlg_audio_file" = "Audio";
|
||||
"lng_in_dlg_contact" = "Contacto";
|
||||
"lng_in_dlg_audio" = "Audio";
|
||||
"lng_in_dlg_audio" = "Mensaje de voz";
|
||||
"lng_in_dlg_file" = "Archivo";
|
||||
"lng_in_dlg_sticker" = "Sticker";
|
||||
"lng_in_dlg_sticker_emoji" = "{emoji} (sticker)";
|
||||
|
@ -678,12 +680,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_report_spam_sure_group" = "¿Quieres reportar el spam en este grupo?";
|
||||
"lng_report_spam_sure_channel" = "¿Quieres reportar a este canal como spam?";
|
||||
"lng_report_spam_ok" = "Reportar";
|
||||
"lng_cant_send_to_not_contact" = "Por ahora, sólo puedes enviar mensajes\na contactos mutuos. {more_info}";
|
||||
"lng_cant_invite_not_contact" = "Por ahora, sólo puedes añadir contactos \nmutuos a grupos. {more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Lo sentimos, sólo puedes añadir contactos\nmutuos a canales. {more_info}";
|
||||
"lng_cant_send_to_not_contact" = "Por ahora, sólo puedes enviar\nmensajes a contactos mutuos. \n{more_info}";
|
||||
"lng_cant_invite_not_contact" = "Por ahora, sólo puedes añadir \ncontactos mutuos a grupos. \n{more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Por ahora, sólo puedes añadir \ncontactos mutuos a canales. \n{more_info}";
|
||||
"lng_cant_more_info" = "Más información »";
|
||||
"lng_cant_invite_banned" = "Sólo el administrador puede añadir a este usuario.";
|
||||
"lng_cant_invite_privacy" = "No puedes añadir a este usuario a grupos por sus ajustes de privacidad.";
|
||||
"lng_cant_invite_privacy_channel" = "No puedes añadir a este usuario a canales por sus ajustes de privacidad.";
|
||||
"lng_cant_do_this" = "Lo sentimos, esta acción no está disponible.";
|
||||
|
||||
"lng_send_button" = "Enviar";
|
||||
"lng_message_ph" = "Escribe un mensaje...";
|
||||
|
@ -717,28 +721,29 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_user_typing" = "{user} está escribiendo";
|
||||
"lng_users_typing" = "{user} y {second_user} están escribiendo";
|
||||
"lng_many_typing" = "{count:_not_used_|# está|# están} escribiendo";
|
||||
"lng_send_action_record_video" = "grabando vídeo";
|
||||
"lng_send_action_record_video" = "grabando un vídeo";
|
||||
"lng_user_action_record_video" = "{user} está grabando un vídeo";
|
||||
"lng_send_action_upload_video" = "enviando vídeo";
|
||||
"lng_send_action_upload_video" = "enviando un vídeo";
|
||||
"lng_user_action_upload_video" = "{user} está enviando un vídeo";
|
||||
"lng_send_action_record_audio" = "grabando audio";
|
||||
"lng_user_action_record_audio" = "{user} está grabando un audio";
|
||||
"lng_send_action_upload_audio" = "enviando audio";
|
||||
"lng_user_action_upload_audio" = "{user} está enviando un audio";
|
||||
"lng_send_action_upload_photo" = "enviando foto";
|
||||
"lng_send_action_record_audio" = "grabando un mensaje de voz";
|
||||
"lng_user_action_record_audio" = "{user} está grabando un mensaje de voz";
|
||||
"lng_send_action_upload_audio" = "enviando un mensaje de voz";
|
||||
"lng_user_action_upload_audio" = "{user} está enviando un mensaje de voz";
|
||||
"lng_send_action_upload_photo" = "enviando una foto";
|
||||
"lng_user_action_upload_photo" = "{user} está enviando una foto";
|
||||
"lng_send_action_upload_file" = "enviando archivo";
|
||||
"lng_send_action_upload_file" = "enviando un archivo";
|
||||
"lng_user_action_upload_file" = "{user} está enviando un archivo";
|
||||
"lng_send_action_geo_location" = "obteniendo ubicación";
|
||||
"lng_user_action_geo_location" = "{user} está obteniendo una ubicación";
|
||||
"lng_send_action_choose_contact" = "eligiendo contacto";
|
||||
"lng_send_action_choose_contact" = "eligiendo un contacto";
|
||||
"lng_user_action_choose_contact" = "{user} está eligiendo un contacto";
|
||||
"lng_unread_bar" = "{count:_not_used_|# mensaje sin leer|# mensajes sin leer}";
|
||||
|
||||
"lng_maps_point" = "Ubicación";
|
||||
"lng_save_photo" = "Guardar imagen";
|
||||
"lng_save_video" = "Guardar vídeo";
|
||||
"lng_save_audio" = "Guardar audio";
|
||||
"lng_save_audio_file" = "Guardar audio";
|
||||
"lng_save_audio" = "Guardar mensaje de voz";
|
||||
"lng_save_file" = "Guardar archivo";
|
||||
"lng_save_downloaded" = "{ready} / {total} {mb}";
|
||||
"lng_duration_and_size" = "{duration}, {size}";
|
||||
|
@ -764,6 +769,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_show_in_folder" = "Mostrar en la carpeta";
|
||||
"lng_context_show_in_finder" = "Mostrar en el Finder";
|
||||
"lng_context_save_video" = "Guardar como...";
|
||||
"lng_context_save_audio_file" = "Guardar como...";
|
||||
"lng_context_save_audio" = "Guardar como...";
|
||||
"lng_context_pack_info" = "Información del pack";
|
||||
"lng_context_pack_add" = "Añadir stickers";
|
||||
|
|
|
@ -127,7 +127,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_edit_deleted" = "Questo messaggio è stato eliminato";
|
||||
"lng_edit_too_long" = "Il tuo messaggio è troppo lungo";
|
||||
"lng_edit_message" = "Modifica messaggio";
|
||||
"lng_edit_message_text" = "Nuovo testo messaggio..";
|
||||
"lng_edit_message_text" = "Nuovo testo messaggio...";
|
||||
"lng_deleted" = "Sconosciuto";
|
||||
"lng_deleted_message" = "Messaggio eliminato";
|
||||
"lng_pinned_message" = "Messaggio fissato";
|
||||
|
@ -162,7 +162,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_code_telegram" = "Per favore inserisci il codice che hai\nappena ricevuto nell'altra app di [b]Telegram[/b].";
|
||||
"lng_code_no_telegram" = "Invia codice via SMS";
|
||||
"lng_code_call" = "Telegram ti chiamerà tra {minutes}:{seconds}";
|
||||
"lng_code_calling" = "Richiedendo una telefonata da Telegram..";
|
||||
"lng_code_calling" = "Richiedo una telefonata da Telegram...";
|
||||
"lng_code_called" = "Telegram ti ha chiamato";
|
||||
|
||||
"lng_bad_phone" = "Numero di telefono non valido. Per favore riprova.";
|
||||
|
@ -201,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_dlg_new_channel_name" = "Nome canale";
|
||||
"lng_no_contacts" = "Non hai contatti";
|
||||
"lng_no_chats" = "Le tua chat saranno qui";
|
||||
"lng_contacts_loading" = "Caricamento..";
|
||||
"lng_contacts_loading" = "Caricamento...";
|
||||
"lng_contacts_not_found" = "Nessun contatto trovato";
|
||||
"lng_dlg_search_chat" = "Cerca in questa chat";
|
||||
"lng_dlg_search_channel" = "Cerca in questo canale";
|
||||
|
@ -210,7 +210,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_save" = "Salva";
|
||||
"lng_settings_upload" = "Imposta foto profilo";
|
||||
"lng_settings_crop_profile" = "Seleziona un riquadro per la tua foto profilo";
|
||||
"lng_settings_uploading_photo" = "Caricamento foto..";
|
||||
"lng_settings_uploading_photo" = "Caricamento foto...";
|
||||
|
||||
"lng_username_title" = "Username";
|
||||
"lng_username_about" = "Puoi scegliere un username su Telegram.\nSe lo fai, le altre persone potranno trovarti\ntramite questo username e contattarti \nsenza conoscere il tuo numero di telefono.\n\nPuoi usare a-z, 0-9 e underscore.\nLa lunghezza minima è di 5 caratteri.";
|
||||
|
@ -247,9 +247,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_auto_update" = "Aggiorna automaticamente";
|
||||
"lng_settings_current_version" = "Versione {version}";
|
||||
"lng_settings_check_now" = "Cerca aggiornamenti";
|
||||
"lng_settings_update_checking" = "Cerco aggiornamenti..";
|
||||
"lng_settings_update_checking" = "Cerco aggiornamenti...";
|
||||
"lng_settings_latest_installed" = "L'ultima versione è installata";
|
||||
"lng_settings_downloading" = "Download aggiornamento {ready} / {total} MB..";
|
||||
"lng_settings_downloading" = "Download aggiornamento {ready} / {total} MB...";
|
||||
"lng_settings_update_ready" = "La nuova versione è pronta";
|
||||
"lng_settings_update_now" = "Riavvia ora";
|
||||
"lng_settings_update_fail" = "Ricerca aggiornamenti fallita :(";
|
||||
|
@ -291,7 +291,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_download_path_failed" = "Il download del file non può iniziare. La causa potrebbe essere una posizione sbagliata per i download.\n\nPuoi cambiare il percorso di download nelle Impostazioni.";
|
||||
"lng_download_path_settings" = "Impostazioni";
|
||||
"lng_download_finish_failed" = "Il download del file non può essere concluso.\n\nVuoi riprovare?";
|
||||
"lng_download_path_clearing" = "Eliminazione..";
|
||||
"lng_download_path_clearing" = "Elimino...";
|
||||
"lng_download_path_cleared" = "Eliminato!";
|
||||
"lng_download_path_clear_failed" = "Eliminazione fallita :(";
|
||||
|
||||
|
@ -300,7 +300,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_images_cached" = "{count:_not_used_|# immagine|# immagini}, {size}";
|
||||
"lng_settings_audios_cached" = "{count:_not_used_|# messaggio vocale|# messaggi vocali}, {size}";
|
||||
"lng_local_storage_clear" = "Elimina tutto";
|
||||
"lng_local_storage_clearing" = "Eliminando..";
|
||||
"lng_local_storage_clearing" = "Elimino...";
|
||||
"lng_local_storage_cleared" = "Eliminato!";
|
||||
"lng_local_storage_clear_failed" = "Eliminazione fallita :(";
|
||||
|
||||
|
@ -331,7 +331,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_passcode_logout" = "Disconnetti";
|
||||
"lng_passcode_need_unblock" = "Devi prima sbloccarmi.";
|
||||
|
||||
"lng_cloud_password_waiting" = "Link di conferma inviato a {email}..";
|
||||
"lng_cloud_password_waiting" = "Link di conferma inviato a {email}...";
|
||||
"lng_cloud_password_change" = "Cambia password";
|
||||
"lng_cloud_password_create" = "Password cloud";
|
||||
"lng_cloud_password_remove" = "Rimuovi password";
|
||||
|
@ -358,9 +358,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cloud_password_is_same" = "La password non è stata cambiata";
|
||||
|
||||
"lng_connection_type" = "Tipo di connessione:";
|
||||
"lng_connection_auto_connecting" = "Predefinita (connetto..)";
|
||||
"lng_connection_auto_connecting" = "Predefinita (connetto...)";
|
||||
"lng_connection_auto" = "Predefinita ({transport} in uso)";
|
||||
"lng_connection_proxy_connecting" = "Connetto tramite proxy..";
|
||||
"lng_connection_proxy_connecting" = "Connetto tramite proxy...";
|
||||
"lng_connection_proxy" = "{transport} con proxy";
|
||||
"lng_connection_header" = "Tipo di connessione";
|
||||
"lng_connection_auto_rb" = "Auto (TCP se disponibile o HTTP)";
|
||||
|
@ -396,7 +396,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_sessions_other_desc" = "Ti puoi connettere a Telegram da altri dispositivi mobili, tablet e desktop usando lo stesso numero. Tutti i tuoi dati saranno sincronizzati istantaneamente.";
|
||||
"lng_sessions_terminate_all" = "Termina tutte le altre sessioni";
|
||||
|
||||
"lng_preview_loading" = "Recupero le info del link..";
|
||||
"lng_preview_loading" = "Recupero le info del link...";
|
||||
|
||||
"lng_profile_chat_unaccessible" = "Gruppo non accessibile";
|
||||
"lng_topbar_info" = "Info";
|
||||
|
@ -439,7 +439,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_sure_kick" = "Rimuovere {user} dal gruppo?";
|
||||
"lng_profile_sure_kick_channel" = "Rimuovere {user} dal canale?";
|
||||
"lng_profile_sure_kick_admin" = "Rimuovere {user} dagli amministratori?";
|
||||
"lng_profile_loading" = "Caricamento..";
|
||||
"lng_profile_loading" = "Caricamento...";
|
||||
"lng_profile_shared_media" = "Media condivisi";
|
||||
"lng_profile_no_media" = "Nessun media in questa chat.";
|
||||
"lng_profile_photos" = "{count:_not_used_|# foto|# foto} »";
|
||||
|
@ -455,6 +455,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_shared_links" = "{count:_not_used_|# link condiviso|# link condivisi} »";
|
||||
"lng_profile_shared_links_header" = "Panoramica link condivisi";
|
||||
"lng_profile_copy_phone" = "Copia numero di telefono";
|
||||
"lng_profile_copy_fullname" = "Copia nome";
|
||||
|
||||
"lng_channel_add_admins" = "Nuovo amministratore";
|
||||
"lng_channel_add_members" = "Aggiungi membri";
|
||||
|
@ -659,12 +660,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_remove" = "Elimina";
|
||||
"lng_stickers_return" = "Annulla";
|
||||
"lng_stickers_restore" = "Ripristina";
|
||||
"lng_stickers_count" = "{count:Caricamento..|# sticker|# sticker}";
|
||||
"lng_stickers_count" = "{count:Caricamento...|# sticker|# sticker}";
|
||||
|
||||
"lng_in_dlg_photo" = "Foto";
|
||||
"lng_in_dlg_video" = "Video";
|
||||
"lng_in_dlg_video" = "File video";
|
||||
"lng_in_dlg_audio_file" = "File audio";
|
||||
"lng_in_dlg_contact" = "Contatto";
|
||||
"lng_in_dlg_audio" = "Audio";
|
||||
"lng_in_dlg_audio" = "Messaggio vocale";
|
||||
"lng_in_dlg_file" = "File";
|
||||
"lng_in_dlg_sticker" = "Sticker";
|
||||
"lng_in_dlg_sticker_emoji" = "{emoji} (sticker)";
|
||||
|
@ -678,18 +680,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_report_spam_sure_group" = "Sei sicuro di voler segnalare dello spam in questo gruppo?";
|
||||
"lng_report_spam_sure_channel" = "Sei sicuro di voler segnalare dello spam in questo canale?";
|
||||
"lng_report_spam_ok" = "Segnala";
|
||||
"lng_cant_send_to_not_contact" = "Spiacenti, ma al momento puoi scrivere\nsolo ai contatti reciproci. {more_info}";
|
||||
"lng_cant_invite_not_contact" = "Spiacenti, ma al momento puoi aggiungere\nai gruppi solo contatti reciproci. {more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Spiacenti, ma al momento puoi aggiungere\nai canali solo contatti reciproci. {more_info}";
|
||||
"lng_cant_send_to_not_contact" = "Spiacenti, ma al momento puoi scrivere\nsolo ai contatti reciproci.\n{more_info}";
|
||||
"lng_cant_invite_not_contact" = "Spiacenti, ma al momento puoi aggiungere\nai gruppi solo contatti reciproci.\n{more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Spiacenti, ma al momento puoi aggiungere\nai canali solo contatti reciproci.\n{more_info}";
|
||||
"lng_cant_more_info" = "Più info »";
|
||||
"lng_cant_invite_banned" = "Spiacenti, solo l'amministratore può aggiungere questo utente.";
|
||||
"lng_cant_invite_privacy" = "Spiacenti, non puoi aggiungere questo utente al gruppo a causa delle sue impostazioni di privacy.";
|
||||
"lng_cant_invite_privacy_channel" = "Spiacenti, non puoi aggiungere questo utente al canale a causa delle sue impostazioni di privacy.";
|
||||
"lng_cant_do_this" = "Spiacenti, questa azione non è disponibile.";
|
||||
|
||||
"lng_send_button" = "Invia";
|
||||
"lng_message_ph" = "Scrivi un messaggio..";
|
||||
"lng_comment_ph" = "Scrivi un commento..";
|
||||
"lng_broadcast_ph" = "Pubblica un post..";
|
||||
"lng_broadcast_silent_ph" = "Post silenzioso..";
|
||||
"lng_message_ph" = "Scrivi un messaggio...";
|
||||
"lng_comment_ph" = "Scrivi un commento...";
|
||||
"lng_broadcast_ph" = "Pubblica un post...";
|
||||
"lng_broadcast_silent_ph" = "Post silenzioso...";
|
||||
"lng_record_cancel" = "Rilascia fuori da qui per annullare";
|
||||
"lng_will_be_notified" = "I post saranno notificati ai membri";
|
||||
"lng_wont_be_notified" = "I post non saranno notificati ai membri";
|
||||
|
@ -721,24 +725,25 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_user_action_record_video" = "{user} sta registrando un video";
|
||||
"lng_send_action_upload_video" = "inviando un video";
|
||||
"lng_user_action_upload_video" = "{user} sta inviando un video";
|
||||
"lng_send_action_record_audio" = "registrando un audio";
|
||||
"lng_user_action_record_audio" = "{user} sta registrando un audio";
|
||||
"lng_send_action_upload_audio" = "inviando un audio";
|
||||
"lng_user_action_upload_audio" = "{user} sta inviando un audio";
|
||||
"lng_send_action_record_audio" = "registrando un messaggio vocale";
|
||||
"lng_user_action_record_audio" = "{user} sta registrando un messaggio vocale";
|
||||
"lng_send_action_upload_audio" = "inviando un messaggio vocale";
|
||||
"lng_user_action_upload_audio" = "{user} sta inviando un messaggio vocale";
|
||||
"lng_send_action_upload_photo" = "inviando una foto";
|
||||
"lng_user_action_upload_photo" = "{user} sta inviando una foto";
|
||||
"lng_send_action_upload_file" = "inviando un file";
|
||||
"lng_user_action_upload_file" = "{user} sta inviando un file";
|
||||
"lng_send_action_geo_location" = "selezionando una posizione";
|
||||
"lng_user_action_geo_location" = "{user} sta selezionando una posizione";
|
||||
"lng_send_action_choose_contact" = "selezionando un contatto";
|
||||
"lng_user_action_choose_contact" = "{user} sta selezionando un contatto";
|
||||
"lng_send_action_geo_location" = "scegliendo una posizione";
|
||||
"lng_user_action_geo_location" = "{user} sta scegliendo una posizione";
|
||||
"lng_send_action_choose_contact" = "scegliendo un contatto";
|
||||
"lng_user_action_choose_contact" = "{user} sta scegliendo un contatto";
|
||||
"lng_unread_bar" = "{count:_not_used_|# messaggio non letto|# messaggi non letti}";
|
||||
|
||||
"lng_maps_point" = "Posizione";
|
||||
"lng_save_photo" = "Salva immagine";
|
||||
"lng_save_video" = "Salva video";
|
||||
"lng_save_audio" = "Salva audio";
|
||||
"lng_save_video" = "Salva file video";
|
||||
"lng_save_audio_file" = "Salva file audio";
|
||||
"lng_save_audio" = "Salva messaggio vocale";
|
||||
"lng_save_file" = "Salva file";
|
||||
"lng_save_downloaded" = "{ready} / {total} {mb}";
|
||||
"lng_duration_and_size" = "{duration}, {size}";
|
||||
|
@ -755,7 +760,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_copy_email" = "Copia indirizzo email";
|
||||
"lng_context_copy_hashtag" = "Copia hashtag";
|
||||
"lng_context_copy_mention" = "Copia username";
|
||||
"lng_context_save_image" = "Salva immagine come..";
|
||||
"lng_context_save_image" = "Salva immagine come...";
|
||||
"lng_context_forward_image" = "Inoltra immagine";
|
||||
"lng_context_delete_image" = "Elimina immagine";
|
||||
"lng_context_copy_image" = "Copia immagine";
|
||||
|
@ -763,11 +768,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_cancel_download" = "Annulla download";
|
||||
"lng_context_show_in_folder" = "Mostra nella cartella";
|
||||
"lng_context_show_in_finder" = "Mostra nel Finder";
|
||||
"lng_context_save_video" = "Salva video come..";
|
||||
"lng_context_save_audio" = "Salva audio come..";
|
||||
"lng_context_save_video" = "Salva file video come...";
|
||||
"lng_context_save_audio_file" = "Salva file audio come...";
|
||||
"lng_context_save_audio" = "Salva messaggio vocale come...";
|
||||
"lng_context_pack_info" = "Mostra sticker";
|
||||
"lng_context_pack_add" = "Aggiungi sticker";
|
||||
"lng_context_save_file" = "Salva file come..";
|
||||
"lng_context_save_file" = "Salva file come...";
|
||||
"lng_context_forward_file" = "Inoltra file";
|
||||
"lng_context_delete_file" = "Elimina file";
|
||||
"lng_context_close_file" = "Chiudi file";
|
||||
|
@ -794,7 +800,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_send_image_too_large" = "Impossibile inviare il file, perchè è più grande di 1.5 GB :(";
|
||||
"lng_send_folder" = "Impossibile inviare «{name}» perchè è una cartella :(";
|
||||
|
||||
"lng_forward_choose" = "Scegli destinatario..";
|
||||
"lng_forward_choose" = "Scegli destinatario...";
|
||||
"lng_forward_cant" = "Spiacenti, impossibile inoltrare qui :(";
|
||||
"lng_forward_confirm" = "Inoltra a {recipient}?";
|
||||
"lng_forward_share_contact" = "Condividi contatto con {recipient}?";
|
||||
|
@ -857,7 +863,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_search_global_results" = "Risultati ricerca globale";
|
||||
|
||||
"lng_media_save_progress" = "{ready} di {total} {mb}";
|
||||
"lng_mediaview_save_as" = "Salva come..";
|
||||
"lng_mediaview_save_as" = "Salva come...";
|
||||
"lng_mediaview_copy" = "Copia";
|
||||
"lng_mediaview_forward" = "Inoltra";
|
||||
"lng_mediaview_delete" = "Elimina";
|
||||
|
|
|
@ -86,8 +86,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cancel" = "취소";
|
||||
"lng_continue" = "계속";
|
||||
"lng_close" = "닫기";
|
||||
"lng_connecting" = "연결중입니다..";
|
||||
"lng_reconnecting" = " {count:지금| #초 후에| #초 후에} 다시 연결합니다..";
|
||||
"lng_connecting" = "Connecting...";
|
||||
"lng_reconnecting" = "Reconnect {count:now|in # s|in # s}...";
|
||||
"lng_reconnecting_try_now" = "다시 시도";
|
||||
|
||||
"lng_status_service_notifications" = "서비스 알림";
|
||||
|
@ -108,7 +108,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_status_lastseen_date" = "{date}에 마지막으로 접속";
|
||||
"lng_status_lastseen_date_time" = "{date}일 {time}에 마지막으로 접속";
|
||||
"lng_status_online" = "온라인";
|
||||
"lng_status_connecting" = "연결중..";
|
||||
"lng_status_connecting" = "connecting...";
|
||||
|
||||
"lng_chat_status_unaccessible" = "그룹 접근 불가";
|
||||
"lng_chat_status_members" = "{count:맴버 없음|#명|#명}";
|
||||
|
@ -127,7 +127,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_edit_deleted" = "메시지는 삭제 되었습니다.";
|
||||
"lng_edit_too_long" = "메시지 길이가 너무 깁니다.";
|
||||
"lng_edit_message" = "메시지 수정";
|
||||
"lng_edit_message_text" = "새로운 메시지 내용.";
|
||||
"lng_edit_message_text" = "New message text...";
|
||||
"lng_deleted" = "알 수 없음";
|
||||
"lng_deleted_message" = "삭제된 메시지";
|
||||
"lng_pinned_message" = "고정된 메시지";
|
||||
|
@ -162,7 +162,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_code_telegram" = "[b]텔레그램[/b] 앱으로 부터 방금 수신받은,\n코드를 입력해주세요.";
|
||||
"lng_code_no_telegram" = "코드를 SMS로 전송";
|
||||
"lng_code_call" = "텔레그램이 {minutes}:{seconds}후에는 전화를 겁니다.";
|
||||
"lng_code_calling" = "텔레그램으로부터 전화 요청을 하고 있습니다..";
|
||||
"lng_code_calling" = "Requesting a call from Telegram...";
|
||||
"lng_code_called" = "텔레그램이 회원님의 전화번호로 전화를 걸었습니다.";
|
||||
|
||||
"lng_bad_phone" = "잘못된 전화번호입니다. 다시 시도해주세요.";
|
||||
|
@ -201,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_dlg_new_channel_name" = "채널명";
|
||||
"lng_no_contacts" = "연락처가 없습니다.";
|
||||
"lng_no_chats" = "대화시 대화방이 존재 할 곳입니다.";
|
||||
"lng_contacts_loading" = "로드중..";
|
||||
"lng_contacts_loading" = "Loading...";
|
||||
"lng_contacts_not_found" = "연락처를 찾을 수 없음";
|
||||
"lng_dlg_search_chat" = "이 채팅에서 검색";
|
||||
"lng_dlg_search_channel" = "이 채널방에서 검색";
|
||||
|
@ -210,7 +210,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_save" = "저장";
|
||||
"lng_settings_upload" = "프로필 이미지 선택";
|
||||
"lng_settings_crop_profile" = "프로필 사진으로 사용할 사각영역을 선택하세요";
|
||||
"lng_settings_uploading_photo" = "사진 업로드중..";
|
||||
"lng_settings_uploading_photo" = "Uploading photo...";
|
||||
|
||||
"lng_username_title" = "아이디";
|
||||
"lng_username_about" = "텔레그램 아이디를 설정할 수 있습니다. \n아이디를 설정하면 회원님의 전화번호를 몰라도 아이디로 회원님을 찾아 대화를 나눌 수 있습니다.\n아이디는 영문, 밑줄, 숫자로 a-z, _, 0-9, \n다섯 글자 이상으로 설정해 주세요.";
|
||||
|
@ -247,9 +247,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_auto_update" = "자동 업데이트";
|
||||
"lng_settings_current_version" = " {version}";
|
||||
"lng_settings_check_now" = "업데이트 확인";
|
||||
"lng_settings_update_checking" = "업데이트 확인 중..";
|
||||
"lng_settings_update_checking" = "Checking for updates...";
|
||||
"lng_settings_latest_installed" = "최신 버전이 설치되어 있습니다.";
|
||||
"lng_settings_downloading" = "업데이트를 다운로드중입니다.. {ready} / {total} MB..";
|
||||
"lng_settings_downloading" = "Downloading update {ready} / {total} MB...";
|
||||
"lng_settings_update_ready" = "새로운 버전을 설치 할 수 있습니다.";
|
||||
"lng_settings_update_now" = "재시작 합니다.";
|
||||
"lng_settings_update_fail" = "업데이트 확인 실패 :(";
|
||||
|
@ -291,7 +291,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_download_path_failed" = "파일 다운로드를 시작 할 수 없습니다. 올바르지 않은 다운로드 경로가 원인 일 수도 있습니다.\n\n설정에 가시면 다운로드 경로를 변경하실 수 있습니다.";
|
||||
"lng_download_path_settings" = "설정";
|
||||
"lng_download_finish_failed" = "파일 다운로드를 끝낼 수 없습니다.\n\n다시 시도하시겠습니까?";
|
||||
"lng_download_path_clearing" = "초기화 중..";
|
||||
"lng_download_path_clearing" = "Clearing...";
|
||||
"lng_download_path_cleared" = "초기화 완료!";
|
||||
"lng_download_path_clear_failed" = "초기화 실패 :(";
|
||||
|
||||
|
@ -300,7 +300,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_images_cached" = "{count:_not_used_|이미지 #개|이미지 #개}, {size}";
|
||||
"lng_settings_audios_cached" = "{count:_not_used_|음성 메시지 #개|음성 메시지 #개}, {size}";
|
||||
"lng_local_storage_clear" = "전체 정리";
|
||||
"lng_local_storage_clearing" = "초기화 중..";
|
||||
"lng_local_storage_clearing" = "Clearing...";
|
||||
"lng_local_storage_cleared" = "초기화 완료!";
|
||||
"lng_local_storage_clear_failed" = "초기화 실패 :(";
|
||||
|
||||
|
@ -331,7 +331,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_passcode_logout" = "로그아웃";
|
||||
"lng_passcode_need_unblock" = "잠금코드를 먼저 해제해주세요.";
|
||||
|
||||
"lng_cloud_password_waiting" = "{email}로 확인 이메일을 전송하였습니다..";
|
||||
"lng_cloud_password_waiting" = "Confirmation link sent to {email}...";
|
||||
"lng_cloud_password_change" = "클라우드 비밀번호 변경";
|
||||
"lng_cloud_password_create" = "클라우드 비밀번호";
|
||||
"lng_cloud_password_remove" = "클라우드 비밀번호 삭제";
|
||||
|
@ -358,9 +358,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cloud_password_is_same" = "비밀번호가 변경되지 않았습니다.";
|
||||
|
||||
"lng_connection_type" = "연결 유형:";
|
||||
"lng_connection_auto_connecting" = "기본값 (연결중..)";
|
||||
"lng_connection_auto_connecting" = "Default (connecting...)";
|
||||
"lng_connection_auto" = "기본값 ({transport} 사용)";
|
||||
"lng_connection_proxy_connecting" = "프록시 연결 중...";
|
||||
"lng_connection_proxy_connecting" = "Connecting through proxy...";
|
||||
"lng_connection_proxy" = "{transport} 프록시 연결";
|
||||
"lng_connection_header" = "연결 유형";
|
||||
"lng_connection_auto_rb" = "자동 (사용 가능하다면 TCP 아니면 HTTP 사용)";
|
||||
|
@ -396,7 +396,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_sessions_other_desc" = "동일한 휴대번호로 다른 휴대기기, 태블릿과 데스크탑에서 텔레그램 로그인이 가능합니다. 모든 데이터는 즉시 동기화 됩니다.";
|
||||
"lng_sessions_terminate_all" = "다른 모든 세션 강제 종료";
|
||||
|
||||
"lng_preview_loading" = "링크 정보를 가져오는 중..";
|
||||
"lng_preview_loading" = "Getting Link Info...";
|
||||
|
||||
"lng_profile_chat_unaccessible" = "그룹에 접근할 수 없습니다.";
|
||||
"lng_topbar_info" = "정보";
|
||||
|
@ -439,7 +439,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_sure_kick" = "{user}를 추방하시겠습니까?";
|
||||
"lng_profile_sure_kick_channel" = "{user}를 추방하시겠습니까?";
|
||||
"lng_profile_sure_kick_admin" = "{user}를 관리자에서 제외 하시겠습니까?";
|
||||
"lng_profile_loading" = "로딩중..";
|
||||
"lng_profile_loading" = "Loading...";
|
||||
"lng_profile_shared_media" = "공유된 미디어";
|
||||
"lng_profile_no_media" = "대화에 미디어가 존재하지 않습니다.";
|
||||
"lng_profile_photos" = "{count:_not_used_|#개의 사진|#개의 사진} »";
|
||||
|
@ -455,6 +455,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_shared_links" = "{count:_not_used_|# 공유된 링크|# 공유된 링크} »";
|
||||
"lng_profile_shared_links_header" = "공유된 링크 현황";
|
||||
"lng_profile_copy_phone" = "전화번호 복사";
|
||||
"lng_profile_copy_fullname" = "Copy name";
|
||||
|
||||
"lng_channel_add_admins" = "새로운 관리자";
|
||||
"lng_channel_add_members" = "구성원 추가";
|
||||
|
@ -659,12 +660,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_remove" = "삭제";
|
||||
"lng_stickers_return" = "실행취소";
|
||||
"lng_stickers_restore" = "복구";
|
||||
"lng_stickers_count" = "{count:Loading..|# 스티커|# 스티커} ";
|
||||
"lng_stickers_count" = "{count:Loading...|# sticker|# stickers}";
|
||||
|
||||
"lng_in_dlg_photo" = "사진";
|
||||
"lng_in_dlg_video" = "비디오";
|
||||
"lng_in_dlg_video" = "Video file";
|
||||
"lng_in_dlg_audio_file" = "Audio file";
|
||||
"lng_in_dlg_contact" = "연락처";
|
||||
"lng_in_dlg_audio" = "음성";
|
||||
"lng_in_dlg_audio" = "Voice message";
|
||||
"lng_in_dlg_file" = "파일";
|
||||
"lng_in_dlg_sticker" = "스티커";
|
||||
"lng_in_dlg_sticker_emoji" = "{emoji} (스티커)";
|
||||
|
@ -678,18 +680,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_report_spam_sure_group" = "선택한 그룹메시지를 스팸으로 신고하시겠습니까?";
|
||||
"lng_report_spam_sure_channel" = "선택한 채널메시지를 스팸으로 신고하시겠습니까?";
|
||||
"lng_report_spam_ok" = "신고하기";
|
||||
"lng_cant_send_to_not_contact" = "죄송하지만, 현재 서로 연락처가 추가된\n회원들끼리만 전송이 가능합니다. {more_info}";
|
||||
"lng_cant_invite_not_contact" = "죄송하지만, 현재 서로 연락처가 추가된\n회원들끼리만 추가 가능합니다. {more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "죄송하지만, 현재 서로 연락처가 추가된\n회원들끼리만 추가 가능합니다. {more_info}";
|
||||
"lng_cant_send_to_not_contact" = "Sorry, you can only send messages to\nmutual contacts at the moment.\n{more_info}";
|
||||
"lng_cant_invite_not_contact" = "Sorry, you can only add mutual contacts\nto groups at the moment.\n{more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Sorry, you can only add mutual contacts\nto channels at the moment.\n{more_info}";
|
||||
"lng_cant_more_info" = "자세한 정보 »";
|
||||
"lng_cant_invite_banned" = "Sorry, only admin can add this user.";
|
||||
"lng_cant_invite_privacy" = "죄송합니다, 개인설정으로 인하여 이 사용자를 그룹에 초대할 수 없습니다.";
|
||||
"lng_cant_invite_privacy_channel" = "죄송합니다, 개인설정으로 인하여 이 사용자를 채널에 초대할 수 없습니다.";
|
||||
"lng_cant_do_this" = "Sorry, this action is unavailable.";
|
||||
|
||||
"lng_send_button" = "보내기";
|
||||
"lng_message_ph" = "메시지 쓰기";
|
||||
"lng_comment_ph" = "코멘트 쓰기..";
|
||||
"lng_broadcast_ph" = "단체메시지 쓰기.";
|
||||
"lng_broadcast_silent_ph" = "음소거 메시지..";
|
||||
"lng_message_ph" = "Write a message...";
|
||||
"lng_comment_ph" = "Write a comment...";
|
||||
"lng_broadcast_ph" = "Broadcast a message...";
|
||||
"lng_broadcast_silent_ph" = "Silent broadcast...";
|
||||
"lng_record_cancel" = "이 영역 밖에서 마우스 클릭을 해제하시면 취소가 됩니다.";
|
||||
"lng_will_be_notified" = "메시지 작성시 구성원들에게 알림이 갑니다.";
|
||||
"lng_wont_be_notified" = "메시지 작성시 구성원들에게 알림이 가지 않습니다.";
|
||||
|
@ -717,28 +721,29 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_user_typing" = "{user}님이 입력중입니다.";
|
||||
"lng_users_typing" = "{user}님과 {second_user}님이 입력중입니다.";
|
||||
"lng_many_typing" = "{count:_not_used_|#명이|#명이} 입력중입니다";
|
||||
"lng_send_action_record_video" = "비디오 녹화 중";
|
||||
"lng_user_action_record_video" = "{user}님이 녹화중입니다.";
|
||||
"lng_send_action_upload_video" = "비디오 전송 중";
|
||||
"lng_user_action_upload_video" = "{user}님이 비디오를 전송 중입니다.";
|
||||
"lng_send_action_record_audio" = "오디오 녹음 중";
|
||||
"lng_user_action_record_audio" = "{user}님이 오디오를 녹음 중입니다.";
|
||||
"lng_send_action_upload_audio" = "오디오 전송 중";
|
||||
"lng_user_action_upload_audio" = "{user}님이 오디오를 전송 중입니다.";
|
||||
"lng_send_action_upload_photo" = "사진 전송 중";
|
||||
"lng_user_action_upload_photo" = "{user}님이 사진을 전송 중입니다.";
|
||||
"lng_send_action_upload_file" = "파일을 전송 중";
|
||||
"lng_user_action_upload_file" = "{user}님이 사진을 전송 중입니다.";
|
||||
"lng_send_action_geo_location" = "위치 선택 중";
|
||||
"lng_user_action_geo_location" = "{user}님이 위치를 선택 중입니다.";
|
||||
"lng_send_action_choose_contact" = "연락처 선택 중";
|
||||
"lng_user_action_choose_contact" = "{user}님이 연락처를 선택 중입니다.";
|
||||
"lng_send_action_record_video" = "recording a video";
|
||||
"lng_user_action_record_video" = "{user} is recording a video";
|
||||
"lng_send_action_upload_video" = "sending a video";
|
||||
"lng_user_action_upload_video" = "{user} is sending a video";
|
||||
"lng_send_action_record_audio" = "recording a voice message";
|
||||
"lng_user_action_record_audio" = "{user} is recording a voice message";
|
||||
"lng_send_action_upload_audio" = "sending a voice message";
|
||||
"lng_user_action_upload_audio" = "{user} is sending a voice message";
|
||||
"lng_send_action_upload_photo" = "sending a photo";
|
||||
"lng_user_action_upload_photo" = "{user} is sending a photo";
|
||||
"lng_send_action_upload_file" = "sending a file";
|
||||
"lng_user_action_upload_file" = "{user} is sending a file";
|
||||
"lng_send_action_geo_location" = "picking a location";
|
||||
"lng_user_action_geo_location" = "{user} is picking a location";
|
||||
"lng_send_action_choose_contact" = "choosing a contact";
|
||||
"lng_user_action_choose_contact" = "{user} is choosing a contact";
|
||||
"lng_unread_bar" = "{count:_not_used_|#개의 읽지 않은 메시지|#개의 읽지 않은 메시지}";
|
||||
|
||||
"lng_maps_point" = "위치";
|
||||
"lng_save_photo" = "사진 저장";
|
||||
"lng_save_video" = "동영상 저장";
|
||||
"lng_save_audio" = "음성 저장";
|
||||
"lng_save_video" = "Save video file";
|
||||
"lng_save_audio_file" = "Save audio file";
|
||||
"lng_save_audio" = "Save voice message";
|
||||
"lng_save_file" = "파일 저장";
|
||||
"lng_save_downloaded" = "{ready} / {total} {mb}";
|
||||
"lng_duration_and_size" = "{duration}, {size}";
|
||||
|
@ -755,7 +760,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_copy_email" = "이메일 복사";
|
||||
"lng_context_copy_hashtag" = "해시태그 복사";
|
||||
"lng_context_copy_mention" = "아이디 복사";
|
||||
"lng_context_save_image" = "이미지를 다른 이름으로 저장..";
|
||||
"lng_context_save_image" = "Save Image As...";
|
||||
"lng_context_forward_image" = "이미지 전달";
|
||||
"lng_context_delete_image" = "이미지 삭제";
|
||||
"lng_context_copy_image" = "이미지 복사";
|
||||
|
@ -763,11 +768,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_cancel_download" = "다운로드 취소";
|
||||
"lng_context_show_in_folder" = "탐색기에서 보기";
|
||||
"lng_context_show_in_finder" = "탐색기에서 보기";
|
||||
"lng_context_save_video" = "비디오 이름을 다른이름으로 저장.";
|
||||
"lng_context_save_audio" = "음성메시지를 다른 이름으로 저장..";
|
||||
"lng_context_save_video" = "Save Video File As...";
|
||||
"lng_context_save_audio_file" = "Save Audio File As...";
|
||||
"lng_context_save_audio" = "Save Voice Message As...";
|
||||
"lng_context_pack_info" = "팩 정보";
|
||||
"lng_context_pack_add" = "스티커 추가";
|
||||
"lng_context_save_file" = "파일을 다른 이름으로 저장..";
|
||||
"lng_context_save_file" = "Save File As...";
|
||||
"lng_context_forward_file" = "파일 전달";
|
||||
"lng_context_delete_file" = "파일 삭제";
|
||||
"lng_context_close_file" = "파일 닫기";
|
||||
|
@ -794,7 +800,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_send_image_too_large" = "파일이 1.5GB 보다 큼으로 전송 할 수 없습니다 :(";
|
||||
"lng_send_folder" = " «{name}»은 폴더이기 때문에 전송 할 수 없습니다 :(";
|
||||
|
||||
"lng_forward_choose" = "수신자를 선택하세요..";
|
||||
"lng_forward_choose" = "Choose recipient...";
|
||||
"lng_forward_cant" = "이쪽으로 전달 할 수 없습니다 :(";
|
||||
"lng_forward_confirm" = "{recipient} 님에게 전달하시겠습니까?";
|
||||
"lng_forward_share_contact" = "{recipient} 님에게 연락처를 공유하시겠습니까?";
|
||||
|
@ -857,7 +863,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_search_global_results" = "아이디 검색 결과";
|
||||
|
||||
"lng_media_save_progress" = "{ready} / {total} {mb}";
|
||||
"lng_mediaview_save_as" = "다른 이름으로 저장하기";
|
||||
"lng_mediaview_save_as" = "Save As...";
|
||||
"lng_mediaview_copy" = "복사하기";
|
||||
"lng_mediaview_forward" = "전달";
|
||||
"lng_mediaview_delete" = "삭제";
|
||||
|
|
|
@ -86,8 +86,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cancel" = "Annuleren";
|
||||
"lng_continue" = "Doorgaan";
|
||||
"lng_close" = "Sluiten";
|
||||
"lng_connecting" = "Verbinden";
|
||||
"lng_reconnecting" = "Opnieuw verbinden {count:nu|over # s|over # s}";
|
||||
"lng_connecting" = "Verbinden...";
|
||||
"lng_reconnecting" = "Opnieuw verbinden {count:nu|over # s|over # s}...";
|
||||
"lng_reconnecting_try_now" = "Probeer nu";
|
||||
|
||||
"lng_status_service_notifications" = "servicemeldingen";
|
||||
|
@ -108,7 +108,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_status_lastseen_date" = "laatst gezien {date}";
|
||||
"lng_status_lastseen_date_time" = "laatst gezien {date} om {time}";
|
||||
"lng_status_online" = "online";
|
||||
"lng_status_connecting" = "verbinden";
|
||||
"lng_status_connecting" = "verbinden...";
|
||||
|
||||
"lng_chat_status_unaccessible" = "groep is ontoegankelijk";
|
||||
"lng_chat_status_members" = "{count:geen leden|# lid|# leden}";
|
||||
|
@ -162,7 +162,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_code_telegram" = "Voer de code in die je zojuist\nhebt ontvangen in je vorige [b]Telegram[/b]-app.";
|
||||
"lng_code_no_telegram" = "Verstuur code via SMS";
|
||||
"lng_code_call" = "Telegram belt je over {minutes}:{seconds}";
|
||||
"lng_code_calling" = "Oproepverzoek versturen naar Telegram";
|
||||
"lng_code_calling" = "Oproepverzoek naar Telegram...";
|
||||
"lng_code_called" = "Telegram heeft je nummer gebeld";
|
||||
|
||||
"lng_bad_phone" = "Ongeldig telefoonnummer. \nProbeer het opnieuw.";
|
||||
|
@ -201,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_dlg_new_channel_name" = "Kanaalnaam";
|
||||
"lng_no_contacts" = "Je hebt geen contacten";
|
||||
"lng_no_chats" = "Hier komen je chats";
|
||||
"lng_contacts_loading" = "Bezig met laden";
|
||||
"lng_contacts_loading" = "Laden...";
|
||||
"lng_contacts_not_found" = "Geen contacten gevonden";
|
||||
"lng_dlg_search_chat" = "Zoek in deze chat";
|
||||
"lng_dlg_search_channel" = "Zoek in dit kanaal";
|
||||
|
@ -210,7 +210,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_save" = "Opslaan";
|
||||
"lng_settings_upload" = "Profielfoto instellen";
|
||||
"lng_settings_crop_profile" = "Kies een vierkant voor je profielfoto";
|
||||
"lng_settings_uploading_photo" = "Foto uploaden";
|
||||
"lng_settings_uploading_photo" = "Foto uploaden...";
|
||||
|
||||
"lng_username_title" = "Gebruikersnaam";
|
||||
"lng_username_about" = "Je kunt hier je gebruikersnaam kiezen. \nHiermee kunnen anderen je vinden \nen contact met je opnemen zonder \nje telefoonnummer te weten.\n\na-z, 0-9 en underscore is toegestaan. \nDe minimale lengte is 5 tekens.";
|
||||
|
@ -247,9 +247,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_auto_update" = "Automatisch bijwerken";
|
||||
"lng_settings_current_version" = "Versie {version}";
|
||||
"lng_settings_check_now" = "Controleer op updates";
|
||||
"lng_settings_update_checking" = "Op updates controleren";
|
||||
"lng_settings_update_checking" = "Controleren op updates...";
|
||||
"lng_settings_latest_installed" = "Meest recente versie is geïnstalleerd.";
|
||||
"lng_settings_downloading" = "Update downloaden {ready} / {total} MB";
|
||||
"lng_settings_downloading" = "Update downloaden {ready} / {total} MB...";
|
||||
"lng_settings_update_ready" = "Nieuwe versie staat klaar";
|
||||
"lng_settings_update_now" = "Nu herstarten";
|
||||
"lng_settings_update_fail" = "Controleren op updates mislukt";
|
||||
|
@ -291,7 +291,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_download_path_failed" = "Download kon niet worden gestart. De oorzaak kan een ongeldige downloadmap zijn.\n\nJe kunt de map aanpassen via instellingen.";
|
||||
"lng_download_path_settings" = "Instellingen";
|
||||
"lng_download_finish_failed" = "Downloaden mislukt.\n\nWil je het opnieuw proberen?";
|
||||
"lng_download_path_clearing" = "Wissen";
|
||||
"lng_download_path_clearing" = "Wissen...";
|
||||
"lng_download_path_cleared" = "Gewist!";
|
||||
"lng_download_path_clear_failed" = "Wissen mislukt";
|
||||
|
||||
|
@ -300,7 +300,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_images_cached" = "{count:_not_used_|# afbeelding|# afbeeldingen}, {size}";
|
||||
"lng_settings_audios_cached" = "{count:_not_used_|# spraakbericht|# spraakberichten}, {size}";
|
||||
"lng_local_storage_clear" = "Alles wissen";
|
||||
"lng_local_storage_clearing" = "Wissen";
|
||||
"lng_local_storage_clearing" = "Wissen...";
|
||||
"lng_local_storage_cleared" = "Gewist!";
|
||||
"lng_local_storage_clear_failed" = "Wissen mislukt";
|
||||
|
||||
|
@ -358,9 +358,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cloud_password_is_same" = "Wachtwoord is niet gewijzigd";
|
||||
|
||||
"lng_connection_type" = "Verbindingstype:";
|
||||
"lng_connection_auto_connecting" = "Standaard (verbinden)";
|
||||
"lng_connection_auto_connecting" = "Standaard (verbinden...)";
|
||||
"lng_connection_auto" = "Standaard ({transport} wordt gebruikt)";
|
||||
"lng_connection_proxy_connecting" = "Verbinden via proxy..";
|
||||
"lng_connection_proxy_connecting" = "Verbinden via proxy...";
|
||||
"lng_connection_proxy" = "{transport} met proxy";
|
||||
"lng_connection_header" = "Verbindingstype";
|
||||
"lng_connection_auto_rb" = "Auto (TCP indien beschikbaar of HTTP)";
|
||||
|
@ -439,7 +439,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_sure_kick" = "{user} uit de groep verwijderen?";
|
||||
"lng_profile_sure_kick_channel" = "{user} uit het kanaal verwijderen?";
|
||||
"lng_profile_sure_kick_admin" = "{user} ontslaan als beheerder?";
|
||||
"lng_profile_loading" = "Bezig met laden";
|
||||
"lng_profile_loading" = "Laden...";
|
||||
"lng_profile_shared_media" = "Gedeelde media";
|
||||
"lng_profile_no_media" = "Geen media in deze chat.";
|
||||
"lng_profile_photos" = "{count:_not_used_|# foto|# foto's} »";
|
||||
|
@ -455,6 +455,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_shared_links" = "{count:_not_used_|# gedeelde link|# gedeelde links} »";
|
||||
"lng_profile_shared_links_header" = "Links-overzicht";
|
||||
"lng_profile_copy_phone" = "Telefoonnummer kopiëren";
|
||||
"lng_profile_copy_fullname" = "Naam kopiëren";
|
||||
|
||||
"lng_channel_add_admins" = "Beheerder toevoegen";
|
||||
"lng_channel_add_members" = "Leden toevoegen";
|
||||
|
@ -662,9 +663,10 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_count" = "{count:Laden..|# sticker|# stickers}";
|
||||
|
||||
"lng_in_dlg_photo" = "Foto";
|
||||
"lng_in_dlg_video" = "Video";
|
||||
"lng_in_dlg_video" = "Video file";
|
||||
"lng_in_dlg_audio_file" = "Audio file";
|
||||
"lng_in_dlg_contact" = "Contact";
|
||||
"lng_in_dlg_audio" = "Geluidsbestand";
|
||||
"lng_in_dlg_audio" = "Voice message";
|
||||
"lng_in_dlg_file" = "Bestand";
|
||||
"lng_in_dlg_sticker" = "Sticker";
|
||||
"lng_in_dlg_sticker_emoji" = "{emoji} (sticker)";
|
||||
|
@ -678,18 +680,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_report_spam_sure_group" = "Spam van deze groep echt melden?";
|
||||
"lng_report_spam_sure_channel" = "Spam van dit kanaal echt melden?";
|
||||
"lng_report_spam_ok" = "Melden";
|
||||
"lng_cant_send_to_not_contact" = "Op dit moment kun je alleen berichten\nversturen naar onderlinge contacten. {more_info}";
|
||||
"lng_cant_invite_not_contact" = "Op dit moment kun je alleen onderlinge\ncontacten aan groepen toevoegen. {more_info}";
|
||||
"lng_cant_send_to_not_contact" = "Je kunt momenteel alleen berichten\nversturen naar onderlinge contacten.\n{more_info}";
|
||||
"lng_cant_invite_not_contact" = "Je kunt momenteel alleen onderlinge\ncontacten aan kanalen toevoegen.\n{more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Je kunt momenteel alleen onderlinge\ncontacten aan kanalen toevoegen.\n{more_info}";
|
||||
"lng_cant_more_info" = "Meer informatie »";
|
||||
"lng_cant_invite_banned" = "Alleen beheerders kunnen deze gebruiker toevoegen.";
|
||||
"lng_cant_invite_privacy" = "Je kunt deze gebruiker niet toevoegen aan groepen door zijn/haar privacyinstellingen.";
|
||||
"lng_cant_invite_privacy_channel" = "Je kunt deze gebruiker niet toevoegen aan kanalen door zijn/haar privacyinstellingen.";
|
||||
"lng_cant_do_this" = "Deze actie is niet beschikbaar.";
|
||||
|
||||
"lng_send_button" = "Stuur";
|
||||
"lng_message_ph" = "Bericht schrijven";
|
||||
"lng_comment_ph" = "Reactie";
|
||||
"lng_broadcast_ph" = "Massabericht";
|
||||
"lng_broadcast_silent_ph" = "Stil massabericht..";
|
||||
"lng_message_ph" = "Bericht...";
|
||||
"lng_comment_ph" = "Reactie...";
|
||||
"lng_broadcast_ph" = "Massabericht...";
|
||||
"lng_broadcast_silent_ph" = "Stil massabericht...";
|
||||
"lng_record_cancel" = "Annuleren: uit het vak loslaten";
|
||||
"lng_will_be_notified" = "Berichtgeving voor leden";
|
||||
"lng_wont_be_notified" = "Geen berichtgeving voor leden";
|
||||
|
@ -721,14 +725,14 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_user_action_record_video" = "{user} neemt video op";
|
||||
"lng_send_action_upload_video" = "video versturen";
|
||||
"lng_user_action_upload_video" = "{user} verstuurt video";
|
||||
"lng_send_action_record_audio" = "geluid opnemen";
|
||||
"lng_user_action_record_audio" = "{user} neemt geluid op";
|
||||
"lng_send_action_upload_audio" = "geluid versturen";
|
||||
"lng_user_action_upload_audio" = "{user} verstuurt geluid";
|
||||
"lng_send_action_record_audio" = "spraakbericht opnemen";
|
||||
"lng_user_action_record_audio" = "{user} neemt spraakbericht op";
|
||||
"lng_send_action_upload_audio" = "spraakbericht versturen";
|
||||
"lng_user_action_upload_audio" = "{user} verstuurt spraakbericht";
|
||||
"lng_send_action_upload_photo" = "foto versturen";
|
||||
"lng_user_action_upload_photo" = "{user} verstuurt een foto";
|
||||
"lng_send_action_upload_file" = "bestand versturen";
|
||||
"lng_user_action_upload_file" = "{user} verstuurt een bestand";
|
||||
"lng_user_action_upload_file" = "{user} verstuurt bestand";
|
||||
"lng_send_action_geo_location" = "locatie kiezen";
|
||||
"lng_user_action_geo_location" = "{user} kiest een locatie";
|
||||
"lng_send_action_choose_contact" = "contact kiezen";
|
||||
|
@ -737,8 +741,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
"lng_maps_point" = "Locatie";
|
||||
"lng_save_photo" = "Afbeelding opslaan";
|
||||
"lng_save_video" = "Video opslaan";
|
||||
"lng_save_audio" = "Geluidsbestand opslaan";
|
||||
"lng_save_video" = "Save video file";
|
||||
"lng_save_audio_file" = "Save audio file";
|
||||
"lng_save_audio" = "Save voice message";
|
||||
"lng_save_file" = "Bestand opslaan";
|
||||
"lng_save_downloaded" = "{ready} / {total} {mb}";
|
||||
"lng_duration_and_size" = "{duration}, {size}";
|
||||
|
@ -755,7 +760,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_copy_email" = "E-mailadres kopiëren";
|
||||
"lng_context_copy_hashtag" = "Hashtag kopiëren";
|
||||
"lng_context_copy_mention" = "Gebruikersnaam kopiëren";
|
||||
"lng_context_save_image" = "Afbeelding opslaan als";
|
||||
"lng_context_save_image" = "Afbeelding opslaan als...";
|
||||
"lng_context_forward_image" = "Afbeelding doorsturen";
|
||||
"lng_context_delete_image" = "Afbeelding verwijderen";
|
||||
"lng_context_copy_image" = "Afbeelding kopiëren";
|
||||
|
@ -763,11 +768,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_cancel_download" = "Download annuleren";
|
||||
"lng_context_show_in_folder" = "Weergeven in map";
|
||||
"lng_context_show_in_finder" = "Weergeven in Finder";
|
||||
"lng_context_save_video" = "Video opslaan als";
|
||||
"lng_context_save_audio" = "Geluidsbestand opslaan als";
|
||||
"lng_context_save_video" = "Save Video File As...";
|
||||
"lng_context_save_audio_file" = "Save Audio File As...";
|
||||
"lng_context_save_audio" = "Save Voice Message As...";
|
||||
"lng_context_pack_info" = "Bundelinformatie";
|
||||
"lng_context_pack_add" = "Stickers toevoegen";
|
||||
"lng_context_save_file" = "Bestand opslaan als";
|
||||
"lng_context_save_file" = "Bestand opslaan als...";
|
||||
"lng_context_forward_file" = "Bestand doorsturen";
|
||||
"lng_context_delete_file" = "Bestand verwijderen";
|
||||
"lng_context_close_file" = "Bestand sluiten";
|
||||
|
@ -794,7 +800,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_send_image_too_large" = "Dit bestand is groter dan 1,5 GB en kan niet worden verstuurd :(";
|
||||
"lng_send_folder" = "Kan de map «{name}» niet versturen, kies een bestand. :(";
|
||||
|
||||
"lng_forward_choose" = "Ontvanger kiezen";
|
||||
"lng_forward_choose" = "Ontvanger kiezen...";
|
||||
"lng_forward_cant" = "Sorry, doorsturen hierheen kan niet :(";
|
||||
"lng_forward_confirm" = "Doorsturen naar {recipient}?";
|
||||
"lng_forward_share_contact" = "Contact delen met {recipient}?";
|
||||
|
@ -857,7 +863,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_search_global_results" = "Wereldwijde zoekresultaten";
|
||||
|
||||
"lng_media_save_progress" = "{ready} van {total} {mb}";
|
||||
"lng_mediaview_save_as" = "Opslaan als";
|
||||
"lng_mediaview_save_as" = "Opslaan als...";
|
||||
"lng_mediaview_copy" = "Kopiëren";
|
||||
"lng_mediaview_forward" = "Doorsturen";
|
||||
"lng_mediaview_delete" = "Verwijder";
|
||||
|
|
|
@ -87,7 +87,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_continue" = "Continuar";
|
||||
"lng_close" = "Fechar";
|
||||
"lng_connecting" = "Conectando...";
|
||||
"lng_reconnecting" = "Reconectar {count:agora|em # s|em # s}..";
|
||||
"lng_reconnecting" = "Reconectar {count:agora|em # s|em # s}...";
|
||||
"lng_reconnecting_try_now" = "Tentar agora";
|
||||
|
||||
"lng_status_service_notifications" = "notificações de serviço";
|
||||
|
@ -127,7 +127,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_edit_deleted" = "Essa mensagem foi apagada";
|
||||
"lng_edit_too_long" = "Sua mensagem está muito longa";
|
||||
"lng_edit_message" = "Editar mensagem";
|
||||
"lng_edit_message_text" = "Nova mensagem...";
|
||||
"lng_edit_message_text" = "Nova mensagem de texto...";
|
||||
"lng_deleted" = "Desconhecido";
|
||||
"lng_deleted_message" = "Mensagem apagada";
|
||||
"lng_pinned_message" = "Mensagem fixada";
|
||||
|
@ -201,7 +201,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_dlg_new_channel_name" = "Nome do canal";
|
||||
"lng_no_contacts" = "Você não possui contatos";
|
||||
"lng_no_chats" = "Seus chats estarão aqui";
|
||||
"lng_contacts_loading" = "Carregando..";
|
||||
"lng_contacts_loading" = "Carregando...";
|
||||
"lng_contacts_not_found" = "Nenhum contato encontrado";
|
||||
"lng_dlg_search_chat" = "Buscar nesse chat";
|
||||
"lng_dlg_search_channel" = "Buscar nesse canal";
|
||||
|
@ -210,7 +210,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_save" = "Salvar";
|
||||
"lng_settings_upload" = "Definir Foto de Perfil";
|
||||
"lng_settings_crop_profile" = "Selecione uma área para a foto do perfil";
|
||||
"lng_settings_uploading_photo" = "Carregando foto..";
|
||||
"lng_settings_uploading_photo" = "Carregando foto...";
|
||||
|
||||
"lng_username_title" = "Nome de usuário";
|
||||
"lng_username_about" = "Você pode escolher um nome de usuário no Telegram.\nAssim, outras pessoas poderão te encontrar\npelo nome de usuário e entrar em contato\nsem precisar saber seu telefone.\n\nVocê pode usar a-z, 0-9 e underline.\nO tamanho mínimo é 5 caracteres.";
|
||||
|
@ -247,9 +247,9 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_auto_update" = "Atualizar automaticamente";
|
||||
"lng_settings_current_version" = "Versão {version}";
|
||||
"lng_settings_check_now" = "Verificar atualizações";
|
||||
"lng_settings_update_checking" = "Verificando atualizações";
|
||||
"lng_settings_update_checking" = "Verificando atualizações...";
|
||||
"lng_settings_latest_installed" = "Última versão está instalada";
|
||||
"lng_settings_downloading" = "Baixando atualização {ready} / {total} MB..";
|
||||
"lng_settings_downloading" = "Baixando atualização {ready} / {total} MB...";
|
||||
"lng_settings_update_ready" = "Nova versão está pronta";
|
||||
"lng_settings_update_now" = "Reiniciar Agora";
|
||||
"lng_settings_update_fail" = "Verificação de atualização falhou :(";
|
||||
|
@ -291,7 +291,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_download_path_failed" = "Download do arquivo não pôde iniciar. Isso pode ter acontecido por um problema na pasta de downlaod.\n\nVocê pode alterar o caminho dos downloads em Configurações.";
|
||||
"lng_download_path_settings" = "Configurações";
|
||||
"lng_download_finish_failed" = "Arquivo baixado não pôde ser finalizado.\n\nGostaria de tentar novamente?";
|
||||
"lng_download_path_clearing" = "Limpando..";
|
||||
"lng_download_path_clearing" = "Limpando...";
|
||||
"lng_download_path_cleared" = "Concluído!";
|
||||
"lng_download_path_clear_failed" = "Limpeza falhou :(";
|
||||
|
||||
|
@ -300,7 +300,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_settings_images_cached" = "{count:_not_used_|# imagem|# imagens}, {size}";
|
||||
"lng_settings_audios_cached" = "{count:_not_used_|# mensagem de voz|# mensagens de voz}, {size}";
|
||||
"lng_local_storage_clear" = "Limpar tudo";
|
||||
"lng_local_storage_clearing" = "Limpando..";
|
||||
"lng_local_storage_clearing" = "Limpando...";
|
||||
"lng_local_storage_cleared" = "Concluído!";
|
||||
"lng_local_storage_clear_failed" = "Limpeza falhou :(";
|
||||
|
||||
|
@ -331,7 +331,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_passcode_logout" = "Sair";
|
||||
"lng_passcode_need_unblock" = "Você precisa me desbloquear primeiro.";
|
||||
|
||||
"lng_cloud_password_waiting" = "Link de confirmação enviado para {email}..";
|
||||
"lng_cloud_password_waiting" = "Link de confirmação enviado para {email}...";
|
||||
"lng_cloud_password_change" = "Alterar senha da nuvem";
|
||||
"lng_cloud_password_create" = "Senha da nuvem";
|
||||
"lng_cloud_password_remove" = "Remover senha da nuvem";
|
||||
|
@ -358,7 +358,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_cloud_password_is_same" = "Senha não alterada";
|
||||
|
||||
"lng_connection_type" = "Tipo de conexão:";
|
||||
"lng_connection_auto_connecting" = "Padrão (conectando..)";
|
||||
"lng_connection_auto_connecting" = "Padrão (conectando...)";
|
||||
"lng_connection_auto" = "Padrão ({transport} usado)";
|
||||
"lng_connection_proxy_connecting" = "Conectando via proxy...";
|
||||
"lng_connection_proxy" = "{transport} com proxy";
|
||||
|
@ -396,7 +396,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_sessions_other_desc" = "Você pode entrar no Telegram de outro celular, tablet e computadores, usando o mesmo número de telefone. Todos seus dados estarão sincronizados.";
|
||||
"lng_sessions_terminate_all" = "Encerrar todas as outras sessões";
|
||||
|
||||
"lng_preview_loading" = "Obtendo informações..";
|
||||
"lng_preview_loading" = "Obtendo Informações do Link...";
|
||||
|
||||
"lng_profile_chat_unaccessible" = "Grupo inacessível";
|
||||
"lng_topbar_info" = "Info";
|
||||
|
@ -439,7 +439,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_sure_kick" = "Remover {user} do grupo?";
|
||||
"lng_profile_sure_kick_channel" = "Remover {user} do canal?";
|
||||
"lng_profile_sure_kick_admin" = "Remover {user} dos administradores?";
|
||||
"lng_profile_loading" = "Carregando..";
|
||||
"lng_profile_loading" = "Carregando...";
|
||||
"lng_profile_shared_media" = "Mídia compartilhada";
|
||||
"lng_profile_no_media" = "Nenhuma mídia nessa conversa.";
|
||||
"lng_profile_photos" = "{count:_not_used_|# foto|# fotos} »";
|
||||
|
@ -455,6 +455,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_profile_shared_links" = "{count:_not_used_|# link|# links} »";
|
||||
"lng_profile_shared_links_header" = "Links";
|
||||
"lng_profile_copy_phone" = "Copiar número de telefone";
|
||||
"lng_profile_copy_fullname" = "Copiar nome";
|
||||
|
||||
"lng_channel_add_admins" = "Novo administrador";
|
||||
"lng_channel_add_members" = "Adicionar membros";
|
||||
|
@ -659,12 +660,13 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_stickers_remove" = "Remover";
|
||||
"lng_stickers_return" = "Desfazer";
|
||||
"lng_stickers_restore" = "Restaurar";
|
||||
"lng_stickers_count" = "{count:Carregando..|# sticker|# stickers}";
|
||||
"lng_stickers_count" = "{count:Carregando...|# sticker|# stickers}";
|
||||
|
||||
"lng_in_dlg_photo" = "Foto";
|
||||
"lng_in_dlg_video" = "Vídeo";
|
||||
"lng_in_dlg_audio_file" = "Áudio";
|
||||
"lng_in_dlg_contact" = "Contato";
|
||||
"lng_in_dlg_audio" = "Áudio";
|
||||
"lng_in_dlg_audio" = "Mensagem de voz";
|
||||
"lng_in_dlg_file" = "Arquivo";
|
||||
"lng_in_dlg_sticker" = "Sticker";
|
||||
"lng_in_dlg_sticker_emoji" = "{emoji} (sticker)";
|
||||
|
@ -678,18 +680,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_report_spam_sure_group" = "Tem certeza que deseja reportar spam desse grupo?";
|
||||
"lng_report_spam_sure_channel" = "Tem certeza que deseja reportar spam desse canal?";
|
||||
"lng_report_spam_ok" = "Reportar";
|
||||
"lng_cant_send_to_not_contact" = "Você só pode enviar mensagens para\ncontatos mútuos no momento. {more_info}";
|
||||
"lng_cant_invite_not_contact" = "Você só pode adicionar contatos\nmútuos ao grupo no momento. {more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Desculpe, você só pode adicionar contatos\nmútuos para canais no momento. {more_info}";
|
||||
"lng_cant_send_to_not_contact" = "Desculpe, você só pode enviar mensagens\npara contatos mútuos no momento.\n{more_info}";
|
||||
"lng_cant_invite_not_contact" = "Desculpe, no momento você só pode adicionar\ncontatos mútuos em grupos.\n{more_info}";
|
||||
"lng_cant_invite_not_contact_channel" = "Desculpe, no momento você só pode adicionar\ncontatos mútuos nos canais.\n{more_info}";
|
||||
"lng_cant_more_info" = "Informações »";
|
||||
"lng_cant_invite_banned" = "Apenas um administrador pode adicionar esse usuário.";
|
||||
"lng_cant_invite_privacy" = "Você não pode adicionar esse usuário em grupos devido as configurações de privacidade.";
|
||||
"lng_cant_invite_privacy_channel" = "Você não pode adicionar esse usuário em canais devido as configurações de privacidade.";
|
||||
"lng_cant_do_this" = "Essa ação não está disponível.";
|
||||
|
||||
"lng_send_button" = "Enviar";
|
||||
"lng_message_ph" = "Escrever a mensagem..";
|
||||
"lng_message_ph" = "Escrever uma mensagem...";
|
||||
"lng_comment_ph" = "Escreva um comentário...";
|
||||
"lng_broadcast_ph" = "Transmitir a mensagem...";
|
||||
"lng_broadcast_silent_ph" = "Silenciar transmissão...";
|
||||
"lng_broadcast_ph" = "Transmitir uma mensagem...";
|
||||
"lng_broadcast_silent_ph" = "Transmissão silenciosa...";
|
||||
"lng_record_cancel" = "Solte fora desse campo para cancelar";
|
||||
"lng_will_be_notified" = "Os membros serão notificados quando você postar";
|
||||
"lng_wont_be_notified" = "Os membros não serão notificados quando você postar";
|
||||
|
@ -717,28 +721,29 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_user_typing" = "{user} está escrevendo";
|
||||
"lng_users_typing" = "{user} e {second_user} estão escrevendo";
|
||||
"lng_many_typing" = "{count:_not_used_|# está|# estão} escrevendo";
|
||||
"lng_send_action_record_video" = "gravando vídeo";
|
||||
"lng_user_action_record_video" = "{user} está gravando vídeo";
|
||||
"lng_send_action_upload_video" = "enviando vídeo";
|
||||
"lng_user_action_upload_video" = "{user} está enviando vídeo";
|
||||
"lng_send_action_record_audio" = "gravando áudio";
|
||||
"lng_user_action_record_audio" = "{user} está gravando áudio";
|
||||
"lng_send_action_upload_audio" = "enviando áudio";
|
||||
"lng_user_action_upload_audio" = "{user} está gravando áudio";
|
||||
"lng_send_action_upload_photo" = "enviando foto";
|
||||
"lng_user_action_upload_photo" = "{user} está enviando foto";
|
||||
"lng_send_action_upload_file" = "enviando arquivo";
|
||||
"lng_user_action_upload_file" = "{user} está enviando arquivo";
|
||||
"lng_send_action_geo_location" = "escolhendo local";
|
||||
"lng_user_action_geo_location" = "{user} está escolhendo local";
|
||||
"lng_send_action_choose_contact" = "escolhendo contato";
|
||||
"lng_user_action_choose_contact" = "{user} está escolhendo contato";
|
||||
"lng_send_action_record_video" = "gravando um vídeo";
|
||||
"lng_user_action_record_video" = "{user} está gravando um vídeo";
|
||||
"lng_send_action_upload_video" = "enviando um vídeo";
|
||||
"lng_user_action_upload_video" = "{user} está enviando um vídeo";
|
||||
"lng_send_action_record_audio" = "gravando uma mensagem de voz";
|
||||
"lng_user_action_record_audio" = "{user} está gravando um áudio";
|
||||
"lng_send_action_upload_audio" = "enviando uma mensagem de voz";
|
||||
"lng_user_action_upload_audio" = "{user} está enviando um áudio";
|
||||
"lng_send_action_upload_photo" = "enviando uma foto";
|
||||
"lng_user_action_upload_photo" = "{user} está enviando uma foto";
|
||||
"lng_send_action_upload_file" = "enviando um arquivo";
|
||||
"lng_user_action_upload_file" = "{user} está enviando um arquivo";
|
||||
"lng_send_action_geo_location" = "escolhendo uma localização";
|
||||
"lng_user_action_geo_location" = "{user} está escolhendo uma localização";
|
||||
"lng_send_action_choose_contact" = "escolhendo um contato";
|
||||
"lng_user_action_choose_contact" = "{user} está escolhendo um contato";
|
||||
"lng_unread_bar" = "{count:_not_used_|# mensagem não lida|# mensagens não lidas}";
|
||||
|
||||
"lng_maps_point" = "Localização";
|
||||
"lng_save_photo" = "Salvar imagem";
|
||||
"lng_save_video" = "Salvar vídeo";
|
||||
"lng_save_audio" = "Salvar áudio";
|
||||
"lng_save_audio_file" = "Salvar áudio";
|
||||
"lng_save_audio" = "Salvar mensagens de voz";
|
||||
"lng_save_file" = "Salvar arquivo";
|
||||
"lng_save_downloaded" = "{ready} / {total} {mb}";
|
||||
"lng_duration_and_size" = "{duration}, {size}";
|
||||
|
@ -755,7 +760,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_copy_email" = "Copiar endereço de email";
|
||||
"lng_context_copy_hashtag" = "Copiar hashtag";
|
||||
"lng_context_copy_mention" = "Copiar nome de usuário";
|
||||
"lng_context_save_image" = "Salvar Imagem Como..";
|
||||
"lng_context_save_image" = "Salvar Imagem Como...";
|
||||
"lng_context_forward_image" = "Encaminhar Imagem";
|
||||
"lng_context_delete_image" = "Apagar Imagem";
|
||||
"lng_context_copy_image" = "Copiar Imagem";
|
||||
|
@ -763,11 +768,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_context_cancel_download" = "Cancelar Download";
|
||||
"lng_context_show_in_folder" = "Mostrar na Pasta";
|
||||
"lng_context_show_in_finder" = "Mostrar no Finder";
|
||||
"lng_context_save_video" = "Salvar Vídeo Como..";
|
||||
"lng_context_save_audio" = "Salvar Áudio Como..";
|
||||
"lng_context_save_video" = "Salvar Vídeo Como...";
|
||||
"lng_context_save_audio_file" = "Salvar Áudio Como...";
|
||||
"lng_context_save_audio" = "Salvar Mensagem de Voz Como...";
|
||||
"lng_context_pack_info" = "Informação do pacote";
|
||||
"lng_context_pack_add" = "Adicionar aos Stickers";
|
||||
"lng_context_save_file" = "Salvar Arquivo Como..";
|
||||
"lng_context_save_file" = "Salvar Arquivo Como...";
|
||||
"lng_context_forward_file" = "Encaminhar Arquivo";
|
||||
"lng_context_delete_file" = "Apagar Arquivo";
|
||||
"lng_context_close_file" = "Fechar Arquivo";
|
||||
|
@ -794,7 +800,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_send_image_too_large" = "Não pude enviar, o arquivo é maior que 1.5GB :(";
|
||||
"lng_send_folder" = "Não pude enviar «{name}» porque é um diretório :(";
|
||||
|
||||
"lng_forward_choose" = "Escolher recipiente..";
|
||||
"lng_forward_choose" = "Escolher recipiente...";
|
||||
"lng_forward_cant" = "Desculpe, não há como encaminhar aqui :(";
|
||||
"lng_forward_confirm" = "Encaminhar para {recipient}?";
|
||||
"lng_forward_share_contact" = "Compartilhar contato com {recipient}?";
|
||||
|
@ -857,7 +863,7 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
"lng_search_global_results" = "Resultados da busca global";
|
||||
|
||||
"lng_media_save_progress" = "{ready} de {total} {mb}";
|
||||
"lng_mediaview_save_as" = "Salvar Como..";
|
||||
"lng_mediaview_save_as" = "Salvar Como...";
|
||||
"lng_mediaview_copy" = "Copiar";
|
||||
"lng_mediaview_forward" = "Encaminhar";
|
||||
"lng_mediaview_delete" = "Apagar";
|
||||
|
|
|
@ -293,9 +293,10 @@ void LayoutAbstractFileItem::setStatusSize(int32 newSize, int32 fullSize, int32
|
|||
}
|
||||
}
|
||||
|
||||
LayoutOverviewDate::LayoutOverviewDate(const QDate &date, bool month) : LayoutItem(OverviewItemInfo::Bit())
|
||||
LayoutOverviewDate::LayoutOverviewDate(const QDate &date, bool month) : LayoutItem()
|
||||
, _date(date)
|
||||
, _text(month ? langMonthFull(date) : langDayOfMonthFull(date)) {
|
||||
AddComponents(OverviewItemInfo::Bit());
|
||||
}
|
||||
|
||||
void LayoutOverviewDate::initDimensions() {
|
||||
|
@ -311,7 +312,7 @@ void LayoutOverviewDate::paint(Painter &p, const QRect &clip, uint32 selection,
|
|||
}
|
||||
}
|
||||
|
||||
LayoutOverviewPhoto::LayoutOverviewPhoto(PhotoData *photo, HistoryItem *parent) : LayoutMediaItem(0, parent)
|
||||
LayoutOverviewPhoto::LayoutOverviewPhoto(PhotoData *photo, HistoryItem *parent) : LayoutMediaItem(parent)
|
||||
, _data(photo)
|
||||
, _link(new PhotoLink(photo))
|
||||
, _goodLoaded(false) {
|
||||
|
@ -385,7 +386,7 @@ void LayoutOverviewPhoto::getState(TextLinkPtr &link, HistoryCursorState &cursor
|
|||
}
|
||||
}
|
||||
|
||||
LayoutOverviewVideo::LayoutOverviewVideo(DocumentData *video, HistoryItem *parent) : LayoutAbstractFileItem(0, parent)
|
||||
LayoutOverviewVideo::LayoutOverviewVideo(DocumentData *video, HistoryItem *parent) : LayoutAbstractFileItem(parent)
|
||||
, _data(video)
|
||||
, _duration(formatDurationText(_data->duration()))
|
||||
, _thumbLoaded(false) {
|
||||
|
@ -533,7 +534,7 @@ void LayoutOverviewVideo::updateStatusText() const {
|
|||
statusSize = _data->uploadOffset;
|
||||
} else if (_data->loading()) {
|
||||
statusSize = _data->loadOffset();
|
||||
} else if (!_data->already().isEmpty()) {
|
||||
} else if (_data->loaded()) {
|
||||
statusSize = FileStatusSizeLoaded;
|
||||
} else {
|
||||
statusSize = FileStatusSizeReady;
|
||||
|
@ -549,9 +550,11 @@ void LayoutOverviewVideo::updateStatusText() const {
|
|||
}
|
||||
}
|
||||
|
||||
LayoutOverviewVoice::LayoutOverviewVoice(DocumentData *voice, HistoryItem *parent) : LayoutAbstractFileItem(OverviewItemInfo::Bit(), parent)
|
||||
LayoutOverviewVoice::LayoutOverviewVoice(DocumentData *voice, HistoryItem *parent) : LayoutAbstractFileItem(parent)
|
||||
, _data(voice)
|
||||
, _namel(new DocumentOpenLink(_data)) {
|
||||
AddComponents(OverviewItemInfo::Bit());
|
||||
|
||||
t_assert(_data->voice() != 0);
|
||||
|
||||
setLinks(new DocumentOpenLink(_data), new DocumentOpenLink(_data), new DocumentCancelLink(_data));
|
||||
|
@ -741,7 +744,7 @@ bool LayoutOverviewVoice::updateStatusText() const {
|
|||
return showPause;
|
||||
}
|
||||
|
||||
LayoutOverviewDocument::LayoutOverviewDocument(DocumentData *document, HistoryItem *parent) : LayoutAbstractFileItem(OverviewItemInfo::Bit(), parent)
|
||||
LayoutOverviewDocument::LayoutOverviewDocument(DocumentData *document, HistoryItem *parent) : LayoutAbstractFileItem(parent)
|
||||
, _data(document)
|
||||
, _msgl(new MessageLink(parent))
|
||||
, _namel(new DocumentOpenLink(_data))
|
||||
|
@ -751,6 +754,8 @@ LayoutOverviewDocument::LayoutOverviewDocument(DocumentData *document, HistoryIt
|
|||
, _namew(st::semiboldFont->width(_name))
|
||||
, _datew(st::normalFont->width(_date))
|
||||
, _colorIndex(documentColorIndex(_data, _ext)) {
|
||||
AddComponents(OverviewItemInfo::Bit());
|
||||
|
||||
setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data));
|
||||
|
||||
setStatusSize(FileStatusSizeReady, _data->size, _data->song() ? _data->song()->duration : -1, 0);
|
||||
|
@ -866,7 +871,9 @@ void LayoutOverviewDocument::paint(Painter &p, const QRect &clip, uint32 selecti
|
|||
if (_data->thumb->loaded()) {
|
||||
if (_thumb.isNull() || loaded != _thumbForLoaded) {
|
||||
_thumbForLoaded = loaded;
|
||||
_thumb = _data->thumb->pixNoCache(_thumbw, 0, true, !_thumbForLoaded, false, st::overviewFileSize, st::overviewFileSize);
|
||||
ImagePixOptions options = ImagePixSmooth;
|
||||
if (!_thumbForLoaded) options |= ImagePixBlurred;
|
||||
_thumb = _data->thumb->pixNoCache(_thumbw, 0, options, st::overviewFileSize, st::overviewFileSize);
|
||||
}
|
||||
p.drawPixmap(rthumb.topLeft(), _thumb);
|
||||
} else {
|
||||
|
@ -1064,12 +1071,9 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
LayoutOverviewLink::LayoutOverviewLink(HistoryMedia *media, HistoryItem *parent) : LayoutMediaItem(OverviewItemInfo::Bit(), parent)
|
||||
, _titlew(0)
|
||||
, _page(0)
|
||||
, _pixw(0)
|
||||
, _pixh(0)
|
||||
, _text(st::msgMinWidth) {
|
||||
LayoutOverviewLink::LayoutOverviewLink(HistoryMedia *media, HistoryItem *parent) : LayoutMediaItem(parent) {
|
||||
AddComponents(OverviewItemInfo::Bit());
|
||||
|
||||
QString text = _parent->originalText();
|
||||
EntitiesInText entities = _parent->originalEntities();
|
||||
|
||||
|
@ -1322,7 +1326,7 @@ LayoutOverviewLink::Link::Link(const QString &url, const QString &text)
|
|||
, lnk(linkFromUrl(url)) {
|
||||
}
|
||||
|
||||
LayoutInlineItem::LayoutInlineItem(InlineResult *result, DocumentData *doc, PhotoData *photo) : LayoutItem(0)
|
||||
LayoutInlineItem::LayoutInlineItem(InlineResult *result, DocumentData *doc, PhotoData *photo) : LayoutItem()
|
||||
, _result(result)
|
||||
, _doc(doc)
|
||||
, _photo(photo)
|
||||
|
@ -1574,7 +1578,7 @@ void LayoutInlineGif::prepareThumb(int32 width, int32 height, const QSize &frame
|
|||
if (doc && !doc->thumb->isNull()) {
|
||||
if (doc->thumb->loaded()) {
|
||||
if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) {
|
||||
_thumb = doc->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height);
|
||||
_thumb = doc->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height);
|
||||
}
|
||||
} else {
|
||||
doc->thumb->load();
|
||||
|
@ -1582,7 +1586,7 @@ void LayoutInlineGif::prepareThumb(int32 width, int32 height, const QSize &frame
|
|||
} else if (_result && !_result->thumb_url.isEmpty()) {
|
||||
if (_result->thumb->loaded()) {
|
||||
if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) {
|
||||
_thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height);
|
||||
_thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height);
|
||||
}
|
||||
} else {
|
||||
_result->thumb->load();
|
||||
|
@ -1792,13 +1796,13 @@ void LayoutInlinePhoto::prepareThumb(int32 width, int32 height, const QSize &fra
|
|||
if (photo) {
|
||||
if (photo->medium->loaded()) {
|
||||
if (!_thumbLoaded || _thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) {
|
||||
_thumb = photo->medium->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height);
|
||||
_thumb = photo->medium->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height);
|
||||
}
|
||||
_thumbLoaded = true;
|
||||
} else {
|
||||
if (photo->thumb->loaded()) {
|
||||
if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) {
|
||||
_thumb = photo->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height);
|
||||
_thumb = photo->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height);
|
||||
}
|
||||
}
|
||||
photo->medium->load();
|
||||
|
@ -1806,7 +1810,7 @@ void LayoutInlinePhoto::prepareThumb(int32 width, int32 height, const QSize &fra
|
|||
} else {
|
||||
if (_result->thumb->loaded()) {
|
||||
if (_thumb.width() != width * cIntRetinaFactor() || _thumb.height() != height * cIntRetinaFactor()) {
|
||||
_thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), true, false, false, width, height);
|
||||
_thumb = _result->thumb->pixNoCache(frame.width() * cIntRetinaFactor(), frame.height() * cIntRetinaFactor(), ImagePixSmooth, width, height);
|
||||
}
|
||||
} else {
|
||||
_result->thumb->load();
|
||||
|
@ -1933,7 +1937,7 @@ void LayoutInlineWebVideo::prepareThumb(int32 width, int32 height) const {
|
|||
w = width;
|
||||
}
|
||||
}
|
||||
_thumb = _result->thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), true, false, false, width, height);
|
||||
_thumb = _result->thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), ImagePixSmooth, width, height);
|
||||
}
|
||||
} else {
|
||||
_result->thumb->load();
|
||||
|
@ -2081,7 +2085,7 @@ void LayoutInlineArticle::prepareThumb(int32 width, int32 height) const {
|
|||
w = width;
|
||||
}
|
||||
}
|
||||
_thumb = _result->thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), true, false, false, width, height);
|
||||
_thumb = _result->thumb->pixNoCache(w * cIntRetinaFactor(), h * cIntRetinaFactor(), ImagePixSmooth, width, height);
|
||||
}
|
||||
} else {
|
||||
_result->thumb->load();
|
||||
|
|
|
@ -101,10 +101,11 @@ public:
|
|||
};
|
||||
|
||||
class LayoutMediaItem;
|
||||
class LayoutItem : public Interfaces {
|
||||
class LayoutItem : public Composer {
|
||||
public:
|
||||
LayoutItem(uint64 i_mask) : Interfaces(i_mask), _maxw(0), _minh(0) {
|
||||
LayoutItem() {
|
||||
}
|
||||
LayoutItem &operator=(const LayoutItem &) = delete;
|
||||
|
||||
int32 maxWidth() const {
|
||||
return _maxw;
|
||||
|
@ -167,14 +168,16 @@ public:
|
|||
}
|
||||
|
||||
protected:
|
||||
int32 _width, _height, _maxw, _minh;
|
||||
LayoutItem &operator=(const LayoutItem &);
|
||||
int _width = 0;
|
||||
int _height = 0;
|
||||
int _maxw = 0;
|
||||
int _minh = 0;
|
||||
|
||||
};
|
||||
|
||||
class LayoutMediaItem : public LayoutItem {
|
||||
public:
|
||||
LayoutMediaItem(uint64 i_mask, HistoryItem *parent) : LayoutItem(i_mask), _parent(parent) {
|
||||
LayoutMediaItem(HistoryItem *parent) : _parent(parent) {
|
||||
}
|
||||
|
||||
virtual LayoutMediaItem *toLayoutMediaItem() {
|
||||
|
@ -194,7 +197,7 @@ protected:
|
|||
|
||||
class LayoutRadialProgressItem : public LayoutMediaItem {
|
||||
public:
|
||||
LayoutRadialProgressItem(uint64 i_mask, HistoryItem *parent) : LayoutMediaItem(i_mask, parent)
|
||||
LayoutRadialProgressItem(HistoryItem *parent) : LayoutMediaItem(parent)
|
||||
, _radial(0)
|
||||
, a_iconOver(0, 0)
|
||||
, _a_iconOver(animation(this, &LayoutRadialProgressItem::step_iconOver)) {
|
||||
|
@ -240,7 +243,7 @@ private:
|
|||
|
||||
class LayoutAbstractFileItem : public LayoutRadialProgressItem {
|
||||
public:
|
||||
LayoutAbstractFileItem(uint64 i_mask, HistoryItem *parent) : LayoutRadialProgressItem(i_mask, parent) {
|
||||
LayoutAbstractFileItem(HistoryItem *parent) : LayoutRadialProgressItem(parent) {
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -268,19 +271,19 @@ public:
|
|||
|
||||
};
|
||||
|
||||
class OverviewItemInfo : public BasicInterface<OverviewItemInfo> {
|
||||
class OverviewItemInfo : public BaseComponent<OverviewItemInfo> {
|
||||
public:
|
||||
OverviewItemInfo(Interfaces *) : _top(0) {
|
||||
OverviewItemInfo(Composer*) {
|
||||
}
|
||||
int32 top() const {
|
||||
int top() const {
|
||||
return _top;
|
||||
}
|
||||
void setTop(int32 top) {
|
||||
void setTop(int top) {
|
||||
_top = top;
|
||||
}
|
||||
|
||||
private:
|
||||
int32 _top;
|
||||
int _top = 0;
|
||||
|
||||
};
|
||||
|
||||
|
@ -440,10 +443,11 @@ private:
|
|||
TextLinkPtr _photol;
|
||||
|
||||
QString _title, _letter;
|
||||
int32 _titlew;
|
||||
WebPageData *_page;
|
||||
int32 _pixw, _pixh;
|
||||
Text _text;
|
||||
int _titlew = 0;
|
||||
WebPageData *_page = nullptr;
|
||||
int _pixw = 0;
|
||||
int _pixh = 0;
|
||||
Text _text = { int(st::msgMinWidth) };
|
||||
|
||||
struct Link {
|
||||
Link() : width(0) {
|
||||
|
|
|
@ -171,7 +171,7 @@ void TaskQueueWorker::onTaskAdded() {
|
|||
_inTaskAdded = false;
|
||||
}
|
||||
|
||||
FileLoadTask::FileLoadTask(const QString &filepath, PrepareMediaType type, const FileLoadTo &to, FileLoadForceConfirmType confirm) : _id(MTP::nonce<uint64>())
|
||||
FileLoadTask::FileLoadTask(const QString &filepath, PrepareMediaType type, const FileLoadTo &to, FileLoadForceConfirmType confirm) : _id(rand_value<uint64>())
|
||||
, _to(to)
|
||||
, _filepath(filepath)
|
||||
, _duration(0)
|
||||
|
@ -180,7 +180,7 @@ FileLoadTask::FileLoadTask(const QString &filepath, PrepareMediaType type, const
|
|||
, _result(0) {
|
||||
}
|
||||
|
||||
FileLoadTask::FileLoadTask(const QByteArray &content, PrepareMediaType type, const FileLoadTo &to) : _id(MTP::nonce<uint64>())
|
||||
FileLoadTask::FileLoadTask(const QByteArray &content, PrepareMediaType type, const FileLoadTo &to) : _id(rand_value<uint64>())
|
||||
, _to(to)
|
||||
, _content(content)
|
||||
, _duration(0)
|
||||
|
@ -189,7 +189,7 @@ FileLoadTask::FileLoadTask(const QByteArray &content, PrepareMediaType type, con
|
|||
, _result(0) {
|
||||
}
|
||||
|
||||
FileLoadTask::FileLoadTask(const QImage &image, PrepareMediaType type, const FileLoadTo &to, FileLoadForceConfirmType confirm, const QString &originalText) : _id(MTP::nonce<uint64>())
|
||||
FileLoadTask::FileLoadTask(const QImage &image, PrepareMediaType type, const FileLoadTo &to, FileLoadForceConfirmType confirm, const QString &originalText) : _id(rand_value<uint64>())
|
||||
, _to(to)
|
||||
, _image(image)
|
||||
, _duration(0)
|
||||
|
@ -199,7 +199,7 @@ FileLoadTask::FileLoadTask(const QImage &image, PrepareMediaType type, const Fil
|
|||
, _result(0) {
|
||||
}
|
||||
|
||||
FileLoadTask::FileLoadTask(const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to) : _id(MTP::nonce<uint64>())
|
||||
FileLoadTask::FileLoadTask(const QByteArray &voice, int32 duration, const VoiceWaveform &waveform, const FileLoadTo &to) : _id(rand_value<uint64>())
|
||||
, _to(to)
|
||||
, _content(voice)
|
||||
, _duration(duration)
|
||||
|
@ -323,7 +323,7 @@ void FileLoadTask::process() {
|
|||
thumb = full;
|
||||
thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0));
|
||||
|
||||
thumbId = MTP::nonce<uint64>();
|
||||
thumbId = rand_value<uint64>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -350,7 +350,7 @@ void FileLoadTask::process() {
|
|||
thumb = full;
|
||||
thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0));
|
||||
|
||||
thumbId = MTP::nonce<uint64>();
|
||||
thumbId = rand_value<uint64>();
|
||||
|
||||
if (filename.endsWith(qstr(".mp4"), Qt::CaseInsensitive)) {
|
||||
filemime = qstr("video/mp4");
|
||||
|
@ -406,12 +406,12 @@ void FileLoadTask::process() {
|
|||
thumb = full;
|
||||
thumbSize = MTP_photoSize(MTP_string(""), MTP_fileLocationUnavailable(MTP_long(0), MTP_int(0), MTP_long(0)), MTP_int(full.width()), MTP_int(full.height()), MTP_int(0));
|
||||
|
||||
thumbId = MTP::nonce<uint64>();
|
||||
thumbId = rand_value<uint64>();
|
||||
}
|
||||
}
|
||||
|
||||
if (voice) {
|
||||
attributes[0] = MTP_documentAttributeAudio(MTP_int(MTPDdocumentAttributeAudio::flag_voice | MTPDdocumentAttributeAudio::flag_waveform), MTP_int(_duration), MTPstring(), MTPstring(), MTP_string(documentWaveformEncode5bit(_waveform)));
|
||||
attributes[0] = MTP_documentAttributeAudio(MTP_flags(MTPDdocumentAttributeAudio::Flag::f_voice | MTPDdocumentAttributeAudio::Flag::f_waveform), MTP_int(_duration), MTPstring(), MTPstring(), MTP_string(documentWaveformEncode5bit(_waveform)));
|
||||
attributes.resize(1);
|
||||
document = MTP_document(MTP_long(_id), MTP_long(0), MTP_int(unixtime()), MTP_string(filemime), MTP_int(filesize), thumbSize, MTP_int(MTP::maindc()), MTP_vector<MTPDocumentAttribute>(attributes));
|
||||
} else {
|
||||
|
|
|
@ -29,13 +29,13 @@ enum PrepareMediaType {
|
|||
};
|
||||
|
||||
struct ToPrepareMedia {
|
||||
ToPrepareMedia(const QString &file, const PeerId &peer, PrepareMediaType t, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), file(file), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
ToPrepareMedia(const QString &file, const PeerId &peer, PrepareMediaType t, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) : id(rand_value<PhotoId>()), file(file), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
}
|
||||
ToPrepareMedia(const QImage &img, const PeerId &peer, PrepareMediaType t, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), img(img), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
ToPrepareMedia(const QImage &img, const PeerId &peer, PrepareMediaType t, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) : id(rand_value<PhotoId>()), img(img), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
}
|
||||
ToPrepareMedia(const QByteArray &data, const PeerId &peer, PrepareMediaType t, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), data(data), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
ToPrepareMedia(const QByteArray &data, const PeerId &peer, PrepareMediaType t, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) : id(rand_value<PhotoId>()), data(data), peer(peer), type(t), duration(0), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
}
|
||||
ToPrepareMedia(const QByteArray &data, int32 duration, const PeerId &peer, PrepareMediaType t, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) : id(MTP::nonce<PhotoId>()), data(data), peer(peer), type(t), duration(duration), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
ToPrepareMedia(const QByteArray &data, int32 duration, const PeerId &peer, PrepareMediaType t, bool broadcast, bool ctrlShiftEnter, MsgId replyTo) : id(rand_value<PhotoId>()), data(data), peer(peer), type(t), duration(duration), ctrlShiftEnter(ctrlShiftEnter), replyTo(replyTo) {
|
||||
}
|
||||
PhotoId id;
|
||||
QString file;
|
||||
|
|
|
@ -19,8 +19,11 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "localstorage.h"
|
||||
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "mainwidget.h"
|
||||
#include "window.h"
|
||||
#include "lang.h"
|
||||
|
@ -110,7 +113,7 @@ namespace {
|
|||
path.reserve(base.size() + 0x11);
|
||||
path += base;
|
||||
do {
|
||||
result = MTP::nonce<FileKey>();
|
||||
result = rand_value<FileKey>();
|
||||
path.resize(base.size());
|
||||
path += toFilePart(result);
|
||||
} while (!result || keyAlreadyUsed(path, options));
|
||||
|
@ -157,8 +160,8 @@ namespace {
|
|||
|
||||
QByteArray _settingsSalt, _passKeySalt, _passKeyEncrypted;
|
||||
|
||||
mtpAuthKey _oldKey, _settingsKey, _passKey, _localKey;
|
||||
void createLocalKey(const QByteArray &pass, QByteArray *salt, mtpAuthKey *result) {
|
||||
MTP::AuthKey _oldKey, _settingsKey, _passKey, _localKey;
|
||||
void createLocalKey(const QByteArray &pass, QByteArray *salt, MTP::AuthKey *result) {
|
||||
uchar key[LocalEncryptKeySize] = { 0 };
|
||||
int32 iterCount = pass.size() ? LocalEncryptIterCount : LocalEncryptNoPwdIterCount; // dont slow down for no password
|
||||
QByteArray newSalt;
|
||||
|
@ -279,7 +282,7 @@ namespace {
|
|||
|
||||
return true;
|
||||
}
|
||||
static QByteArray prepareEncrypted(EncryptedDescriptor &data, const mtpAuthKey &key = _localKey) {
|
||||
static QByteArray prepareEncrypted(EncryptedDescriptor &data, const MTP::AuthKey &key = _localKey) {
|
||||
data.finish();
|
||||
QByteArray &toEncrypt(data.data);
|
||||
|
||||
|
@ -293,11 +296,11 @@ namespace {
|
|||
*(uint32*)toEncrypt.data() = size;
|
||||
QByteArray encrypted(0x10 + fullSize, Qt::Uninitialized); // 128bit of sha1 - key128, sizeof(data), data
|
||||
hashSha1(toEncrypt.constData(), toEncrypt.size(), encrypted.data());
|
||||
aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, &key, encrypted.constData());
|
||||
MTP::aesEncryptLocal(toEncrypt.constData(), encrypted.data() + 0x10, fullSize, &key, encrypted.constData());
|
||||
|
||||
return encrypted;
|
||||
}
|
||||
bool writeEncrypted(EncryptedDescriptor &data, const mtpAuthKey &key = _localKey) {
|
||||
bool writeEncrypted(EncryptedDescriptor &data, const MTP::AuthKey &key = _localKey) {
|
||||
return writeData(prepareEncrypted(data, key));
|
||||
}
|
||||
void finish() {
|
||||
|
@ -465,7 +468,7 @@ namespace {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, const mtpAuthKey &key = _localKey) {
|
||||
bool decryptLocal(EncryptedDescriptor &result, const QByteArray &encrypted, const MTP::AuthKey &key = _localKey) {
|
||||
if (encrypted.size() <= 16 || (encrypted.size() & 0x0F)) {
|
||||
LOG(("App Error: bad encrypted part size: %1").arg(encrypted.size()));
|
||||
return false;
|
||||
|
@ -501,7 +504,7 @@ namespace {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool readEncryptedFile(FileReadDescriptor &result, const QString &name, int options = UserPath | SafePath, const mtpAuthKey &key = _localKey) {
|
||||
bool readEncryptedFile(FileReadDescriptor &result, const QString &name, int options = UserPath | SafePath, const MTP::AuthKey &key = _localKey) {
|
||||
if (!readFile(result, name, options)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -531,7 +534,7 @@ namespace {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool readEncryptedFile(FileReadDescriptor &result, const FileKey &fkey, int options = UserPath | SafePath, const mtpAuthKey &key = _localKey) {
|
||||
bool readEncryptedFile(FileReadDescriptor &result, const FileKey &fkey, int options = UserPath | SafePath, const MTP::AuthKey &key = _localKey) {
|
||||
return readEncryptedFile(result, toFilePart(fkey), options, key);
|
||||
}
|
||||
|
||||
|
@ -864,7 +867,7 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
mtpDcOptions *_dcOpts = 0;
|
||||
MTP::DcOptions *_dcOpts = 0;
|
||||
bool _readSetting(quint32 blockId, QDataStream &stream, int version) {
|
||||
switch (blockId) {
|
||||
case dbiDcOptionOld: {
|
||||
|
@ -873,16 +876,17 @@ namespace {
|
|||
stream >> dcId >> host >> ip >> port;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
if (_dcOpts) _dcOpts->insert(dcId, mtpDcOption(dcId, 0, ip.toUtf8().constData(), port));
|
||||
if (_dcOpts) _dcOpts->insert(dcId, MTP::DcOption(dcId, 0, ip.toUtf8().constData(), port));
|
||||
} break;
|
||||
|
||||
case dbiDcOption: {
|
||||
quint32 dcIdWithShift, flags, port;
|
||||
quint32 dcIdWithShift, port;
|
||||
qint32 flags;
|
||||
QString ip;
|
||||
stream >> dcIdWithShift >> flags >> ip >> port;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
if (_dcOpts) _dcOpts->insert(dcIdWithShift, mtpDcOption(dcIdWithShift % _mtp_internal::dcShift, flags, ip.toUtf8().constData(), port));
|
||||
if (_dcOpts) _dcOpts->insert(dcIdWithShift, MTP::DcOption(MTP::bareDcId(dcIdWithShift), MTPDdcOption::Flags(flags), ip.toUtf8().constData(), port));
|
||||
} break;
|
||||
|
||||
case dbiChatSizeMax: {
|
||||
|
@ -927,8 +931,8 @@ namespace {
|
|||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
DEBUG_LOG(("MTP Info: key found, dc %1, key: %2").arg(dcId).arg(Logs::mb(key, 256).str()));
|
||||
dcId = dcId % _mtp_internal::dcShift;
|
||||
mtpAuthKeyPtr keyPtr(new mtpAuthKey());
|
||||
dcId = MTP::bareDcId(dcId);
|
||||
MTP::AuthKeyPtr keyPtr(new MTP::AuthKey());
|
||||
keyPtr->setKey(key);
|
||||
keyPtr->setDC(dcId);
|
||||
|
||||
|
@ -1366,7 +1370,7 @@ namespace {
|
|||
bool result = false;
|
||||
QFile file(cWorkingDir() + qsl("tdata/config"));
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
LOG(("App Info: reading old config.."));
|
||||
LOG(("App Info: reading old config..."));
|
||||
QDataStream stream(&file);
|
||||
stream.setVersion(QDataStream::Qt_5_1);
|
||||
|
||||
|
@ -1446,7 +1450,7 @@ namespace {
|
|||
QBuffer decryptedStream(&decrypted);
|
||||
decryptedStream.open(QIODevice::ReadOnly);
|
||||
decryptedStream.seek(4); // skip size
|
||||
LOG(("App Info: reading encrypted old user config.."));
|
||||
LOG(("App Info: reading encrypted old user config..."));
|
||||
|
||||
_readOldUserSettingsFields(&decryptedStream, version);
|
||||
} else if (!_readSetting(blockId, stream, version)) {
|
||||
|
@ -1459,19 +1463,19 @@ namespace {
|
|||
bool result = false;
|
||||
QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString()) + qsl("_config"));
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
LOG(("App Info: reading old user config.."));
|
||||
LOG(("App Info: reading old user config..."));
|
||||
qint32 version = 0;
|
||||
|
||||
mtpDcOptions dcOpts;
|
||||
MTP::DcOptions dcOpts;
|
||||
{
|
||||
QReadLocker lock(MTP::dcOptionsMutex());
|
||||
dcOpts = cDcOptions();
|
||||
dcOpts = Global::DcOptions();
|
||||
}
|
||||
_dcOpts = &dcOpts;
|
||||
_readOldUserSettingsFields(&file, version);
|
||||
{
|
||||
QWriteLocker lock(MTP::dcOptionsMutex());
|
||||
cSetDcOptions(dcOpts);
|
||||
Global::SetDcOptions(dcOpts);
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
@ -1533,7 +1537,7 @@ namespace {
|
|||
QBuffer decryptedStream(&decrypted);
|
||||
decryptedStream.open(QIODevice::ReadOnly);
|
||||
decryptedStream.seek(4); // skip size
|
||||
LOG(("App Info: reading encrypted old keys.."));
|
||||
LOG(("App Info: reading encrypted old keys..."));
|
||||
|
||||
_readOldMtpDataFields(&decryptedStream, version);
|
||||
} else if (!_readSetting(blockId, stream, version)) {
|
||||
|
@ -1546,19 +1550,19 @@ namespace {
|
|||
bool result = false;
|
||||
QFile file(cWorkingDir() + cDataFile() + (cTestMode() ? qsl("_test") : QString()));
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
LOG(("App Info: reading old keys.."));
|
||||
LOG(("App Info: reading old keys..."));
|
||||
qint32 version = 0;
|
||||
|
||||
mtpDcOptions dcOpts;
|
||||
MTP::DcOptions dcOpts;
|
||||
{
|
||||
QReadLocker lock(MTP::dcOptionsMutex());
|
||||
dcOpts = cDcOptions();
|
||||
dcOpts = Global::DcOptions();
|
||||
}
|
||||
_dcOpts = &dcOpts;
|
||||
_readOldMtpDataFields(&file, version);
|
||||
{
|
||||
QWriteLocker lock(MTP::dcOptionsMutex());
|
||||
cSetDcOptions(dcOpts);
|
||||
Global::SetDcOptions(dcOpts);
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
@ -1643,7 +1647,7 @@ namespace {
|
|||
return _writeUserSettings();
|
||||
}
|
||||
|
||||
LOG(("App Info: reading encrypted user settings.."));
|
||||
LOG(("App Info: reading encrypted user settings..."));
|
||||
while (!userSettings.stream.atEnd()) {
|
||||
quint32 blockId;
|
||||
userSettings.stream >> blockId;
|
||||
|
@ -1664,16 +1668,16 @@ namespace {
|
|||
return;
|
||||
}
|
||||
|
||||
mtpKeysMap keys = MTP::getKeys();
|
||||
MTP::AuthKeysMap keys = MTP::getKeys();
|
||||
|
||||
quint32 size = sizeof(quint32) + sizeof(qint32) + sizeof(quint32);
|
||||
size += keys.size() * (sizeof(quint32) + sizeof(quint32) + 256);
|
||||
|
||||
EncryptedDescriptor data(size);
|
||||
data.stream << quint32(dbiUser) << qint32(MTP::authedId()) << quint32(MTP::maindc());
|
||||
for (mtpKeysMap::const_iterator i = keys.cbegin(), e = keys.cend(); i != e; ++i) {
|
||||
data.stream << quint32(dbiKey) << quint32((*i)->getDC());
|
||||
(*i)->write(data.stream);
|
||||
for_const (const MTP::AuthKeyPtr &key, keys) {
|
||||
data.stream << quint32(dbiKey) << quint32(key->getDC());
|
||||
key->write(data.stream);
|
||||
}
|
||||
|
||||
mtp.writeEncrypted(data, _localKey);
|
||||
|
@ -1689,7 +1693,7 @@ namespace {
|
|||
return;
|
||||
}
|
||||
|
||||
LOG(("App Info: reading encrypted mtp data.."));
|
||||
LOG(("App Info: reading encrypted mtp data..."));
|
||||
while (!mtp.stream.atEnd()) {
|
||||
quint32 blockId;
|
||||
mtp.stream >> blockId;
|
||||
|
@ -1715,7 +1719,7 @@ namespace {
|
|||
if (!readFile(mapData, qsl("map"))) {
|
||||
return Local::ReadMapFailed;
|
||||
}
|
||||
LOG(("App Info: reading map.."));
|
||||
LOG(("App Info: reading map..."));
|
||||
|
||||
QByteArray salt, keyEncrypted, mapEncrypted;
|
||||
mapData.stream >> salt >> keyEncrypted >> mapEncrypted;
|
||||
|
@ -1731,7 +1735,7 @@ namespace {
|
|||
|
||||
EncryptedDescriptor keyData, map;
|
||||
if (!decryptLocal(keyData, keyEncrypted, _passKey)) {
|
||||
LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password.."));
|
||||
LOG(("App Info: could not decrypt pass-protected key from map file, maybe bad password..."));
|
||||
return Local::ReadMapPassNeeded;
|
||||
}
|
||||
uchar key[LocalEncryptKeySize] = { 0 };
|
||||
|
@ -1748,7 +1752,7 @@ namespace {
|
|||
LOG(("App Error: could not decrypt map."));
|
||||
return Local::ReadMapFailed;
|
||||
}
|
||||
LOG(("App Info: reading encrypted map.."));
|
||||
LOG(("App Info: reading encrypted map..."));
|
||||
|
||||
DraftsMap draftsMap, draftCursorsMap;
|
||||
DraftsNotReadMap draftsNotReadMap;
|
||||
|
@ -2097,7 +2101,7 @@ namespace Local {
|
|||
_readOldMtpData(false); // needed further in _readMtpData
|
||||
return writeSettings();
|
||||
}
|
||||
LOG(("App Info: reading settings.."));
|
||||
LOG(("App Info: reading settings..."));
|
||||
|
||||
QByteArray salt, settingsEncrypted;
|
||||
settingsData.stream >> salt >> settingsEncrypted;
|
||||
|
@ -2113,16 +2117,16 @@ namespace Local {
|
|||
|
||||
EncryptedDescriptor settings;
|
||||
if (!decryptLocal(settings, settingsEncrypted, _settingsKey)) {
|
||||
LOG(("App Error: could not decrypt settings from settings file, maybe bad passcode.."));
|
||||
LOG(("App Error: could not decrypt settings from settings file, maybe bad passcode..."));
|
||||
return writeSettings();
|
||||
}
|
||||
mtpDcOptions dcOpts;
|
||||
MTP::DcOptions dcOpts;
|
||||
{
|
||||
QReadLocker lock(MTP::dcOptionsMutex());
|
||||
dcOpts = cDcOptions();
|
||||
dcOpts = Global::DcOptions();
|
||||
}
|
||||
_dcOpts = &dcOpts;
|
||||
LOG(("App Info: reading encrypted settings.."));
|
||||
LOG(("App Info: reading encrypted settings..."));
|
||||
while (!settings.stream.atEnd()) {
|
||||
quint32 blockId;
|
||||
settings.stream >> blockId;
|
||||
|
@ -2137,20 +2141,23 @@ namespace Local {
|
|||
if (dcOpts.isEmpty()) {
|
||||
const BuiltInDc *bdcs = builtInDcs();
|
||||
for (int i = 0, l = builtInDcsCount(); i < l; ++i) {
|
||||
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, 0, bdcs[i].ip, bdcs[i].port));
|
||||
MTPDdcOption::Flags flags = 0;
|
||||
MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcs[i].id, flags);
|
||||
dcOpts.insert(idWithShift, MTP::DcOption(bdcs[i].id, flags, bdcs[i].ip, bdcs[i].port));
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port));
|
||||
}
|
||||
|
||||
const BuiltInDc *bdcsipv6 = builtInDcsIPv6();
|
||||
for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) {
|
||||
int32 flags = MTPDdcOption::flag_ipv6, idWithShift = bdcsipv6[i].id + (flags * _mtp_internal::dcShift);
|
||||
dcOpts.insert(idWithShift, mtpDcOption(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port));
|
||||
MTPDdcOption::Flags flags = MTPDdcOption::Flag::f_ipv6;
|
||||
MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcsipv6[i].id, flags);
|
||||
dcOpts.insert(idWithShift, MTP::DcOption(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port));
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port));
|
||||
}
|
||||
}
|
||||
{
|
||||
QWriteLocker lock(MTP::dcOptionsMutex());
|
||||
cSetDcOptions(dcOpts);
|
||||
Global::SetDcOptions(dcOpts);
|
||||
}
|
||||
|
||||
_oldSettingsVersion = settingsData.version;
|
||||
|
@ -2173,30 +2180,34 @@ namespace Local {
|
|||
}
|
||||
settings.writeData(_settingsSalt);
|
||||
|
||||
mtpDcOptions dcOpts;
|
||||
MTP::DcOptions dcOpts;
|
||||
{
|
||||
QReadLocker lock(MTP::dcOptionsMutex());
|
||||
dcOpts = cDcOptions();
|
||||
dcOpts = Global::DcOptions();
|
||||
}
|
||||
if (dcOpts.isEmpty()) {
|
||||
const BuiltInDc *bdcs = builtInDcs();
|
||||
for (int i = 0, l = builtInDcsCount(); i < l; ++i) {
|
||||
dcOpts.insert(bdcs[i].id, mtpDcOption(bdcs[i].id, 0, bdcs[i].ip, bdcs[i].port));
|
||||
MTPDdcOption::Flags flags = 0;
|
||||
MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcs[i].id, flags);
|
||||
dcOpts.insert(idWithShift, MTP::DcOption(bdcs[i].id, flags, bdcs[i].ip, bdcs[i].port));
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 connect option: %2:%3").arg(bdcs[i].id).arg(bdcs[i].ip).arg(bdcs[i].port));
|
||||
}
|
||||
|
||||
const BuiltInDc *bdcsipv6 = builtInDcsIPv6();
|
||||
for (int i = 0, l = builtInDcsCountIPv6(); i < l; ++i) {
|
||||
dcOpts.insert(bdcsipv6[i].id + (MTPDdcOption::flag_ipv6 * _mtp_internal::dcShift), mtpDcOption(bdcsipv6[i].id, MTPDdcOption::flag_ipv6, bdcsipv6[i].ip, bdcsipv6[i].port));
|
||||
MTPDdcOption::Flags flags = MTPDdcOption::Flag::f_ipv6;
|
||||
MTP::ShiftedDcId idWithShift = MTP::shiftDcId(bdcsipv6[i].id, flags);
|
||||
dcOpts.insert(idWithShift, MTP::DcOption(bdcsipv6[i].id, flags, bdcsipv6[i].ip, bdcsipv6[i].port));
|
||||
DEBUG_LOG(("MTP Info: adding built in DC %1 IPv6 connect option: %2:%3").arg(bdcsipv6[i].id).arg(bdcsipv6[i].ip).arg(bdcsipv6[i].port));
|
||||
}
|
||||
|
||||
QWriteLocker lock(MTP::dcOptionsMutex());
|
||||
cSetDcOptions(dcOpts);
|
||||
Global::SetDcOptions(dcOpts);
|
||||
}
|
||||
|
||||
quint32 size = 12 * (sizeof(quint32) + sizeof(qint32));
|
||||
for (mtpDcOptions::const_iterator i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) {
|
||||
for (auto i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) {
|
||||
size += sizeof(quint32) + sizeof(quint32) + sizeof(quint32);
|
||||
size += sizeof(quint32) + _stringSize(QString::fromUtf8(i->ip.data(), i->ip.size()));
|
||||
}
|
||||
|
@ -2223,9 +2234,9 @@ namespace Local {
|
|||
data.stream << quint32(dbiLastUpdateCheck) << qint32(cLastUpdateCheck());
|
||||
data.stream << quint32(dbiScale) << qint32(cConfigScale());
|
||||
data.stream << quint32(dbiLang) << qint32(cLang());
|
||||
for (mtpDcOptions::const_iterator i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) {
|
||||
for (auto i = dcOpts.cbegin(), e = dcOpts.cend(); i != e; ++i) {
|
||||
data.stream << quint32(dbiDcOption) << quint32(i.key());
|
||||
data.stream << quint32(i->flags) << QString::fromUtf8(i->ip.data(), i->ip.size());
|
||||
data.stream << qint32(i->flags) << QString::fromUtf8(i->ip.data(), i->ip.size());
|
||||
data.stream << quint32(i->port);
|
||||
}
|
||||
data.stream << quint32(dbiLangFile) << cLangFile();
|
||||
|
@ -2280,7 +2291,7 @@ namespace Local {
|
|||
}
|
||||
|
||||
bool checkPasscode(const QByteArray &passcode) {
|
||||
mtpAuthKey tmp;
|
||||
MTP::AuthKey tmp;
|
||||
createLocalKey(passcode, &_passKeySalt, &tmp);
|
||||
return (tmp == _passKey);
|
||||
}
|
||||
|
@ -3019,10 +3030,10 @@ namespace Local {
|
|||
}
|
||||
|
||||
void _writeStickerSet(QDataStream &stream, uint64 setId) {
|
||||
StickerSets::const_iterator it = cStickerSets().constFind(setId);
|
||||
if (it == cStickerSets().cend()) return;
|
||||
auto it = Global::StickerSets().constFind(setId);
|
||||
if (it == Global::StickerSets().cend()) return;
|
||||
|
||||
bool notLoaded = (it->flags & MTPDstickerSet_flag_NOT_LOADED);
|
||||
bool notLoaded = (it->flags & MTPDstickerSet_ClientFlag::f_not_loaded);
|
||||
if (notLoaded) {
|
||||
stream << quint64(it->id) << quint64(it->access) << it->title << it->shortName << qint32(-it->count) << qint32(it->hash) << qint32(it->flags);
|
||||
return;
|
||||
|
@ -3063,7 +3074,7 @@ namespace Local {
|
|||
void writeStickers() {
|
||||
if (!_working()) return;
|
||||
|
||||
const StickerSets &sets(cStickerSets());
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
if (sets.isEmpty()) {
|
||||
if (_stickersKey) {
|
||||
clearKey(_stickersKey);
|
||||
|
@ -3075,10 +3086,10 @@ namespace Local {
|
|||
int32 setsCount = 0;
|
||||
QByteArray hashToWrite;
|
||||
quint32 size = sizeof(quint32) + _bytearraySize(hashToWrite);
|
||||
for (StickerSets::const_iterator i = sets.cbegin(); i != sets.cend(); ++i) {
|
||||
bool notLoaded = (i->flags & MTPDstickerSet_flag_NOT_LOADED);
|
||||
for (auto i = sets.cbegin(); i != sets.cend(); ++i) {
|
||||
bool notLoaded = (i->flags & MTPDstickerSet_ClientFlag::f_not_loaded);
|
||||
if (notLoaded) {
|
||||
if (!(i->flags & MTPDstickerSet::flag_disabled) || (i->flags & MTPDstickerSet::flag_official)) { // waiting to receive
|
||||
if (!(i->flags & MTPDstickerSet::Flag::f_disabled) || (i->flags & MTPDstickerSet::Flag::f_official)) { // waiting to receive
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
|
@ -3114,8 +3125,8 @@ namespace Local {
|
|||
}
|
||||
EncryptedDescriptor data(size);
|
||||
data.stream << quint32(setsCount) << hashToWrite;
|
||||
_writeStickerSet(data.stream, CustomStickerSetId);
|
||||
for (StickerSetsOrder::const_iterator i = cStickerSetsOrder().cbegin(), e = cStickerSetsOrder().cend(); i != e; ++i) {
|
||||
_writeStickerSet(data.stream, Stickers::CustomSetId);
|
||||
for (auto i = Global::StickerSetsOrder().cbegin(), e = Global::StickerSetsOrder().cend(); i != e; ++i) {
|
||||
_writeStickerSet(data.stream, *i);
|
||||
}
|
||||
FileWriteDescriptor file(_stickersKey);
|
||||
|
@ -3134,17 +3145,17 @@ namespace Local {
|
|||
return;
|
||||
}
|
||||
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
sets.clear();
|
||||
|
||||
StickerSetsOrder &order(cRefStickerSetsOrder());
|
||||
Stickers::Order &order(Global::RefStickerSetsOrder());
|
||||
order.clear();
|
||||
|
||||
RecentStickerPack &recent(cRefRecentStickers());
|
||||
recent.clear();
|
||||
|
||||
StickerSet &def(sets.insert(DefaultStickerSetId, StickerSet(DefaultStickerSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::flag_official)).value());
|
||||
StickerSet &custom(sets.insert(CustomStickerSetId, StickerSet(CustomStickerSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0)).value());
|
||||
Stickers::Set &def(sets.insert(Stickers::DefaultSetId, Stickers::Set(Stickers::DefaultSetId, 0, lang(lng_stickers_default_set), QString(), 0, 0, MTPDstickerSet::Flag::f_official)).value());
|
||||
Stickers::Set &custom(sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0)).value());
|
||||
|
||||
QMap<uint64, bool> read;
|
||||
while (!stickers.stream.atEnd()) {
|
||||
|
@ -3183,11 +3194,11 @@ namespace Local {
|
|||
if (recent.size() < StickerPanPerRow * StickerPanRowsPerPage && qAbs(value) > 1) recent.push_back(qMakePair(doc, qAbs(value)));
|
||||
}
|
||||
if (def.stickers.isEmpty()) {
|
||||
sets.remove(DefaultStickerSetId);
|
||||
sets.remove(Stickers::DefaultSetId);
|
||||
} else {
|
||||
order.push_front(DefaultStickerSetId);
|
||||
order.push_front(Stickers::DefaultSetId);
|
||||
}
|
||||
if (custom.stickers.isEmpty()) sets.remove(CustomStickerSetId);
|
||||
if (custom.stickers.isEmpty()) sets.remove(Stickers::CustomSetId);
|
||||
|
||||
writeStickers();
|
||||
writeUserSettings();
|
||||
|
@ -3210,10 +3221,10 @@ namespace Local {
|
|||
return;
|
||||
}
|
||||
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
sets.clear();
|
||||
|
||||
StickerSetsOrder &order(cRefStickerSetsOrder());
|
||||
Stickers::Order &order(Global::RefStickerSetsOrder());
|
||||
order.clear();
|
||||
|
||||
quint32 cnt;
|
||||
|
@ -3231,20 +3242,24 @@ namespace Local {
|
|||
qint32 setHash = 0, setFlags = 0;
|
||||
if (stickers.version > 8033) {
|
||||
stickers.stream >> setHash >> setFlags;
|
||||
if (setFlags & qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old)) {
|
||||
setFlags &= ~qFlags(MTPDstickerSet_ClientFlag::f_not_loaded__old);
|
||||
setFlags |= qFlags(MTPDstickerSet_ClientFlag::f_not_loaded);
|
||||
}
|
||||
}
|
||||
|
||||
if (setId == DefaultStickerSetId) {
|
||||
if (setId == Stickers::DefaultSetId) {
|
||||
setTitle = lang(lng_stickers_default_set);
|
||||
setFlags |= MTPDstickerSet::flag_official;
|
||||
setFlags |= qFlags(MTPDstickerSet::Flag::f_official);
|
||||
order.push_front(setId);
|
||||
} else if (setId == CustomStickerSetId) {
|
||||
} else if (setId == Stickers::CustomSetId) {
|
||||
setTitle = lang(lng_custom_stickers);
|
||||
} else if (setId) {
|
||||
order.push_back(setId);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
StickerSet &set(sets.insert(setId, StickerSet(setId, setAccess, setTitle, setShortName, 0, setHash, setFlags)).value());
|
||||
Stickers::Set &set(sets.insert(setId, Stickers::Set(setId, setAccess, setTitle, setShortName, 0, setHash, MTPDstickerSet::Flags(setFlags))).value());
|
||||
if (scnt < 0) { // disabled not loaded set
|
||||
set.count = -scnt;
|
||||
continue;
|
||||
|
@ -3264,7 +3279,7 @@ namespace Local {
|
|||
if (read.contains(id)) continue;
|
||||
read.insert(id, true);
|
||||
|
||||
if (setId == DefaultStickerSetId || setId == CustomStickerSetId) {
|
||||
if (setId == Stickers::DefaultSetId || setId == Stickers::CustomSetId) {
|
||||
typeOfSet = StickerSetTypeEmpty;
|
||||
}
|
||||
|
||||
|
@ -3325,17 +3340,17 @@ namespace Local {
|
|||
int32 countStickersHash(bool checkOfficial) {
|
||||
uint32 acc = 0;
|
||||
bool foundOfficial = false, foundBad = false;;
|
||||
const StickerSets &sets(cStickerSets());
|
||||
const StickerSetsOrder &order(cStickerSetsOrder());
|
||||
for (StickerSetsOrder::const_iterator i = order.cbegin(), e = order.cend(); i != e; ++i) {
|
||||
StickerSets::const_iterator j = sets.constFind(*i);
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
const Stickers::Order &order(Global::StickerSetsOrder());
|
||||
for (auto i = order.cbegin(), e = order.cend(); i != e; ++i) {
|
||||
auto j = sets.constFind(*i);
|
||||
if (j != sets.cend()) {
|
||||
if (j->id == 0) {
|
||||
foundBad = true;
|
||||
} else if (j->flags & MTPDstickerSet::flag_official) {
|
||||
} else if (j->flags & MTPDstickerSet::Flag::f_official) {
|
||||
foundOfficial = true;
|
||||
}
|
||||
if (!(j->flags & MTPDstickerSet::flag_disabled)) {
|
||||
if (!(j->flags & MTPDstickerSet::Flag::f_disabled)) {
|
||||
acc = (acc * 20261) + j->hash;
|
||||
}
|
||||
}
|
||||
|
@ -3535,7 +3550,7 @@ namespace Local {
|
|||
return result;
|
||||
}
|
||||
|
||||
void _writePeer(QDataStream &stream, PeerData *peer, int32 fileVersion = AppVersion) {
|
||||
void _writePeer(QDataStream &stream, PeerData *peer) {
|
||||
stream << quint64(peer->id) << quint64(peer->photoId);
|
||||
_writeStorageImageLocation(stream, peer->photoLoc);
|
||||
if (peer->isUser()) {
|
||||
|
@ -3545,7 +3560,7 @@ namespace Local {
|
|||
if (AppVersion >= 9012) {
|
||||
stream << qint32(user->flags);
|
||||
}
|
||||
if (AppVersion >= 9016 || fileVersion >= 9016) {
|
||||
if (AppVersion >= 9016) {
|
||||
stream << (user->botInfo ? user->botInfo->inlinePlaceholder : QString());
|
||||
}
|
||||
stream << qint32(user->onlineTill) << qint32(user->contact) << qint32(user->botInfo ? user->botInfo->version : -1);
|
||||
|
@ -3565,18 +3580,16 @@ namespace Local {
|
|||
}
|
||||
|
||||
PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) {
|
||||
PeerData *result = 0;
|
||||
quint64 peerId = 0, photoId = 0;
|
||||
from.stream >> peerId >> photoId;
|
||||
|
||||
StorageImageLocation photoLoc(_readStorageImageLocation(from));
|
||||
|
||||
result = App::peerLoaded(peerId);
|
||||
bool wasLoaded = (result && result->loaded);
|
||||
|
||||
PeerData *result = App::peerLoaded(peerId);
|
||||
bool wasLoaded = (result != nullptr);
|
||||
if (!wasLoaded) {
|
||||
result = App::peer(peerId);
|
||||
result->loaded = true;
|
||||
result->loadedStatus = PeerData::FullLoaded;
|
||||
}
|
||||
if (result->isUser()) {
|
||||
UserData *user = result->asUser();
|
||||
|
@ -3600,7 +3613,7 @@ namespace Local {
|
|||
user->setName(first, last, pname, username);
|
||||
|
||||
user->access = access;
|
||||
user->flags = flags;
|
||||
user->flags = MTPDuser::Flags(flags);
|
||||
user->onlineTill = onlineTill;
|
||||
user->contact = contact;
|
||||
user->setBotInfoVersion(botInfoVersion);
|
||||
|
@ -3616,7 +3629,7 @@ namespace Local {
|
|||
user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long((user->access == UserNoAccess) ? 0 : user->access));
|
||||
}
|
||||
|
||||
user->photo = photoLoc.isNull() ? ImagePtr(userDefPhoto(user->colorIndex)) : ImagePtr(photoLoc);
|
||||
user->setUserpic(photoLoc.isNull() ? ImagePtr(userDefPhoto(user->colorIndex)) : ImagePtr(photoLoc));
|
||||
}
|
||||
} else if (result->isChat()) {
|
||||
ChatData *chat = result->asChat();
|
||||
|
@ -3629,7 +3642,7 @@ namespace Local {
|
|||
flags = flagsData;
|
||||
} else {
|
||||
// flagsData was haveLeft
|
||||
flags = (flagsData == 1 ? MTPDchat::flag_left : 0);
|
||||
flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0);
|
||||
}
|
||||
if (!wasLoaded) {
|
||||
chat->updateName(name, QString(), QString());
|
||||
|
@ -3638,20 +3651,20 @@ namespace Local {
|
|||
chat->version = version;
|
||||
chat->creator = creator;
|
||||
chat->isForbidden = (forbidden == 1);
|
||||
chat->flags = flags;
|
||||
chat->flags = MTPDchat::Flags(flags);
|
||||
chat->invitationUrl = invitationUrl;
|
||||
|
||||
chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id)));
|
||||
chat->inputChat = MTP_int(peerToChat(chat->id));
|
||||
|
||||
chat->photo = photoLoc.isNull() ? ImagePtr(chatDefPhoto(chat->colorIndex)) : ImagePtr(photoLoc);
|
||||
chat->setUserpic(photoLoc.isNull() ? ImagePtr(chatDefPhoto(chat->colorIndex)) : ImagePtr(photoLoc));
|
||||
}
|
||||
} else if (result->isChannel()) {
|
||||
ChannelData *channel = result->asChannel();
|
||||
|
||||
QString name, invitationUrl;
|
||||
quint64 access;
|
||||
qint32 date, version, adminned, forbidden, flags;
|
||||
qint32 date, version, forbidden, flags;
|
||||
from.stream >> name >> access >> date >> version >> forbidden >> flags >> invitationUrl;
|
||||
|
||||
if (!wasLoaded) {
|
||||
|
@ -3660,13 +3673,13 @@ namespace Local {
|
|||
channel->date = date;
|
||||
channel->version = version;
|
||||
channel->isForbidden = (forbidden == 1);
|
||||
channel->flags = flags;
|
||||
channel->flags = MTPDchannel::Flags(flags);
|
||||
channel->invitationUrl = invitationUrl;
|
||||
|
||||
channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access));
|
||||
channel->inputChannel = MTP_inputChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access));
|
||||
|
||||
channel->photo = photoLoc.isNull() ? ImagePtr((channel->isMegagroup() ? chatDefPhoto(channel->colorIndex) : channelDefPhoto(channel->colorIndex))) : ImagePtr(photoLoc);
|
||||
channel->setUserpic(photoLoc.isNull() ? ImagePtr((channel->isMegagroup() ? chatDefPhoto(channel->colorIndex) : channelDefPhoto(channel->colorIndex))) : ImagePtr(photoLoc));
|
||||
}
|
||||
}
|
||||
if (!wasLoaded) {
|
||||
|
@ -3722,7 +3735,7 @@ namespace Local {
|
|||
}
|
||||
data.stream << quint32(botsCnt);
|
||||
for (RecentInlineBots::const_iterator i = bots.cbegin(), e = bots.cend(); i != e; ++i) {
|
||||
_writePeer(data.stream, *i, 9016);
|
||||
_writePeer(data.stream, *i);
|
||||
}
|
||||
FileWriteDescriptor file(_recentHashtagsAndBotsKey);
|
||||
file.writeEncrypted(data);
|
||||
|
|
|
@ -19,9 +19,14 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "logs.h"
|
||||
|
||||
#include <signal.h>
|
||||
#include "pspecific.h"
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
// see https://blog.inventic.eu/2012/08/qt-and-google-breakpad/
|
||||
#ifdef Q_OS_WIN
|
||||
|
||||
|
@ -30,17 +35,20 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#include "client/windows/handler/exception_handler.h"
|
||||
#pragma warning(pop)
|
||||
|
||||
#elif defined Q_OS_MAC
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
|
||||
#include <unistd.h>
|
||||
#ifdef MAC_USE_BREAKPAD
|
||||
#include "client/mac/handler/exception_handler.h"
|
||||
#else
|
||||
#else // MAC_USE_BREAKPAD
|
||||
#include "client/crashpad_client.h"
|
||||
#endif
|
||||
#endif // else for MAC_USE_BREAKPAD
|
||||
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
|
||||
#include "client/linux/handler/exception_handler.h"
|
||||
#endif
|
||||
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
enum LogDataType {
|
||||
LogDataMain,
|
||||
|
@ -79,7 +87,7 @@ QString _logsEntryStart() {
|
|||
QDateTime tm(QDateTime::currentDateTime());
|
||||
|
||||
QThread *thread = QThread::currentThread();
|
||||
MTPThread *mtpThread = qobject_cast<MTPThread*>(thread);
|
||||
MTP::internal::Thread *mtpThread = qobject_cast<MTP::internal::Thread*>(thread);
|
||||
uint threadId = mtpThread ? mtpThread->getThreadId() : 0;
|
||||
|
||||
return QString("[%1 %2-%3]").arg(tm.toString("hh:mm:ss.zzz")).arg(QString("%1").arg(threadId, 2, 10, QChar('0'))).arg(++index, 7, 10, QChar('0'));
|
||||
|
@ -286,8 +294,8 @@ void _logsWrite(LogDataType type, const QString &msg) {
|
|||
void _moveOldDataFiles(const QString &from);
|
||||
|
||||
namespace SignalHandlers {
|
||||
void StartBreakpad();
|
||||
void FinishBreakpad();
|
||||
void StartCrashHandler();
|
||||
void FinishCrashHandler();
|
||||
}
|
||||
|
||||
namespace Logs {
|
||||
|
@ -303,22 +311,22 @@ namespace Logs {
|
|||
QString initialWorkingDir = QDir(cWorkingDir()).absolutePath() + '/', moveOldDataFrom;
|
||||
if (cBetaVersion()) {
|
||||
cSetDebug(true);
|
||||
#if (defined Q_OS_MAC || defined Q_OS_LINUX)
|
||||
#if defined Q_OS_MAC || defined Q_OS_LINUX
|
||||
} else {
|
||||
#ifdef _DEBUG
|
||||
cForceWorkingDir(cExeDir());
|
||||
#else
|
||||
#else // _DEBUG
|
||||
if (cWorkingDir().isEmpty()) {
|
||||
cForceWorkingDir(psAppDataPath());
|
||||
}
|
||||
#endif
|
||||
#endif // else for _DEBUG
|
||||
workingDirChosen = true;
|
||||
|
||||
#if (defined Q_OS_LINUX && !defined _DEBUG) // fix first version
|
||||
#if defined Q_OS_LINUX && !defined _DEBUG // fix first version
|
||||
moveOldDataFrom = initialWorkingDir;
|
||||
#endif
|
||||
#endif // Q_OS_LINUX && !_DEBUG
|
||||
|
||||
#endif
|
||||
#endif // Q_OS_MAC || Q_OS_LINUX
|
||||
}
|
||||
|
||||
LogsData = new LogsDataFields();
|
||||
|
@ -337,7 +345,7 @@ namespace Logs {
|
|||
QDir().mkpath(cWorkingDir() + qstr("tdata"));
|
||||
|
||||
Sandbox::WorkingDirReady();
|
||||
SignalHandlers::StartBreakpad();
|
||||
SignalHandlers::StartCrashHandler();
|
||||
|
||||
if (!LogsData->openMain()) {
|
||||
delete LogsData;
|
||||
|
@ -389,7 +397,7 @@ namespace Logs {
|
|||
|
||||
_logsMutex(LogDataMain, true);
|
||||
|
||||
SignalHandlers::FinishBreakpad();
|
||||
SignalHandlers::FinishCrashHandler();
|
||||
}
|
||||
|
||||
bool started() {
|
||||
|
@ -606,6 +614,11 @@ void _moveOldDataFiles(const QString &wasDir) {
|
|||
|
||||
namespace SignalHandlers {
|
||||
|
||||
typedef std::map<std::string, std::string> AnnotationsMap;
|
||||
AnnotationsMap ProcessAnnotations;
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
QString CrashDumpPath;
|
||||
FILE *CrashDumpFile = nullptr;
|
||||
int CrashDumpFileNo = 0;
|
||||
|
@ -642,14 +655,26 @@ namespace SignalHandlers {
|
|||
return stream;
|
||||
}
|
||||
|
||||
template <bool Unsigned, typename Type>
|
||||
struct _writeNumberSignAndRemoveIt {
|
||||
static void call(Type &number) {
|
||||
if (number < 0) {
|
||||
_writeChar('-');
|
||||
number = -number;
|
||||
}
|
||||
}
|
||||
};
|
||||
template <typename Type>
|
||||
struct _writeNumberSignAndRemoveIt<true, Type> {
|
||||
static void call(Type &number) {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Type>
|
||||
const dump &_writeNumber(const dump &stream, Type number) {
|
||||
if (!CrashDumpFile) return stream;
|
||||
|
||||
if (number < 0) {
|
||||
_writeChar('-');
|
||||
number = -number;
|
||||
}
|
||||
_writeNumberSignAndRemoveIt<(Type(-1) > Type(0)), Type>::call(number);
|
||||
Type upper = 1, prev = number / 10;
|
||||
while (prev >= upper) {
|
||||
upper *= 10;
|
||||
|
@ -700,9 +725,6 @@ namespace SignalHandlers {
|
|||
bool LoggingCrashHeaderWritten = false;
|
||||
QMutex LoggingCrashMutex;
|
||||
|
||||
typedef std::map<std::string, std::string> AnnotationsMap;
|
||||
AnnotationsMap ProcessAnnotations;
|
||||
|
||||
const char *BreakpadDumpPath = 0;
|
||||
const wchar_t *BreakpadDumpPathW = 0;
|
||||
|
||||
|
@ -714,9 +736,9 @@ namespace SignalHandlers {
|
|||
sigaction(signum, &SIG_def[signum], 0);
|
||||
}
|
||||
|
||||
#else
|
||||
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
void Handler(int signum) {
|
||||
#endif
|
||||
#endif // else for Q_OS_MAC || Q_OS_LINUX || Q_OS_LINUX64
|
||||
|
||||
const char* name = 0;
|
||||
switch (signum) {
|
||||
|
@ -727,7 +749,7 @@ namespace SignalHandlers {
|
|||
#ifndef Q_OS_WIN
|
||||
case SIGBUS: name = "SIGBUS"; break;
|
||||
case SIGSYS: name = "SIGSYS"; break;
|
||||
#endif
|
||||
#endif // !Q_OS_WIN
|
||||
}
|
||||
|
||||
Qt::HANDLE thread = QThread::currentThreadId();
|
||||
|
@ -817,17 +839,17 @@ namespace SignalHandlers {
|
|||
dump() << "_unknown_module_\n";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // Q_OS_MAC
|
||||
|
||||
dump() << "\nBacktrace:\n";
|
||||
|
||||
backtrace_symbols_fd(addresses, size, CrashDumpFileNo);
|
||||
|
||||
#else
|
||||
#else // Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
dump() << "\nBacktrace:\n";
|
||||
|
||||
psWriteStackTrace();
|
||||
#endif
|
||||
#endif // else for Q_OS_MAC || Q_OS_LINUX32 || Q_OS_LINUX64
|
||||
|
||||
dump() << "\n";
|
||||
|
||||
|
@ -841,11 +863,11 @@ namespace SignalHandlers {
|
|||
|
||||
#ifdef Q_OS_WIN
|
||||
bool DumpCallback(const wchar_t* _dump_dir, const wchar_t* _minidump_id, void* context, EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion, bool success)
|
||||
#elif defined Q_OS_MAC
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
bool DumpCallback(const char* _dump_dir, const char* _minidump_id, void *context, bool success)
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32 // Q_OS_MAC
|
||||
bool DumpCallback(const google_breakpad::MinidumpDescriptor &md, void *context, bool success)
|
||||
#endif
|
||||
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
|
||||
{
|
||||
if (CrashLogged) return success;
|
||||
CrashLogged = true;
|
||||
|
@ -853,20 +875,23 @@ namespace SignalHandlers {
|
|||
#ifdef Q_OS_WIN
|
||||
BreakpadDumpPathW = _minidump_id;
|
||||
Handler(-1);
|
||||
#else
|
||||
#else // Q_OS_WIN
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
BreakpadDumpPath = _minidump_id;
|
||||
#else
|
||||
#else // Q_OS_MAC
|
||||
BreakpadDumpPath = md.path();
|
||||
#endif
|
||||
#endif // else for Q_OS_MAC
|
||||
Handler(-1, 0, 0);
|
||||
#endif
|
||||
#endif // else for Q_OS_WIN
|
||||
return success;
|
||||
}
|
||||
#endif
|
||||
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
|
||||
|
||||
void StartBreakpad() {
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
void StartCrashHandler() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
ProcessAnnotations["Binary"] = cExeName().toUtf8().constData();
|
||||
ProcessAnnotations["ApiId"] = QString::number(ApiId).toUtf8().constData();
|
||||
ProcessAnnotations["Version"] = (cBetaVersion() ? qsl("%1 beta").arg(cBetaVersion()) : (cDevVersion() ? qsl("%1 dev") : qsl("%1")).arg(AppVersion)).toUtf8().constData();
|
||||
|
@ -885,7 +910,7 @@ namespace SignalHandlers {
|
|||
/*context*/ 0,
|
||||
true
|
||||
);
|
||||
#elif defined Q_OS_MAC
|
||||
#elif defined Q_OS_MAC // Q_OS_WIN
|
||||
|
||||
#ifdef MAC_USE_BREAKPAD
|
||||
#ifndef _DEBUG
|
||||
|
@ -897,9 +922,9 @@ namespace SignalHandlers {
|
|||
true,
|
||||
0
|
||||
);
|
||||
#endif
|
||||
#endif // !_DEBUG
|
||||
SetSignalHandlers = false;
|
||||
#else
|
||||
#else // MAC_USE_BREAKPAD
|
||||
crashpad::CrashpadClient crashpad_client;
|
||||
std::string handler = (cExeDir() + cExeName() + qsl("/Contents/Helpers/crashpad_handler")).toUtf8().constData();
|
||||
std::string database = QFile::encodeName(dumpspath).constData();
|
||||
|
@ -911,7 +936,7 @@ namespace SignalHandlers {
|
|||
false)) {
|
||||
crashpad_client.UseHandler();
|
||||
}
|
||||
#endif
|
||||
#endif // else for MAC_USE_BREAKPAD
|
||||
#elif defined Q_OS_LINUX64 || defined Q_OS_LINUX32
|
||||
BreakpadExceptionHandler = new google_breakpad::ExceptionHandler(
|
||||
google_breakpad::MinidumpDescriptor(QFile::encodeName(dumpspath).toStdString()),
|
||||
|
@ -921,26 +946,36 @@ namespace SignalHandlers {
|
|||
true,
|
||||
-1
|
||||
);
|
||||
#endif
|
||||
#endif // Q_OS_LINUX64 || Q_OS_LINUX32
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
void FinishBreakpad() {
|
||||
void FinishCrashHandler() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
|
||||
#if !defined Q_OS_MAC || defined MAC_USE_BREAKPAD
|
||||
if (BreakpadExceptionHandler) {
|
||||
google_breakpad::ExceptionHandler *h = BreakpadExceptionHandler;
|
||||
BreakpadExceptionHandler = 0;
|
||||
delete h;
|
||||
}
|
||||
#endif
|
||||
#endif // !Q_OS_MAC || MAC_USE_BREAKPAD
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
Status start() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
CrashDumpPath = cWorkingDir() + qsl("tdata/working");
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (FILE *f = _wfopen(CrashDumpPath.toStdWString().c_str(), L"rb")) {
|
||||
#else
|
||||
FILE *f = nullptr;
|
||||
if (_wfopen_s(&f, CrashDumpPath.toStdWString().c_str(), L"rb") != 0) {
|
||||
f = nullptr;
|
||||
} else {
|
||||
#else // !Q_OS_WIN
|
||||
if (FILE *f = fopen(QFile::encodeName(CrashDumpPath).constData(), "rb")) {
|
||||
#endif
|
||||
#endif // else for !Q_OS_WIN
|
||||
QByteArray lastdump;
|
||||
char buffer[256 * 1024] = { 0 };
|
||||
int32 read = fread(buffer, 1, 256 * 1024, f);
|
||||
|
@ -955,21 +990,30 @@ namespace SignalHandlers {
|
|||
|
||||
return LastCrashed;
|
||||
}
|
||||
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
return restart();
|
||||
}
|
||||
|
||||
Status restart() {
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
if (CrashDumpFile) {
|
||||
return Started;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
CrashDumpFile = _wfopen(CrashDumpPath.toStdWString().c_str(), L"wb");
|
||||
#else
|
||||
if (_wfopen_s(&CrashDumpFile, CrashDumpPath.toStdWString().c_str(), L"wb") != 0) {
|
||||
CrashDumpFile = nullptr;
|
||||
}
|
||||
#else // Q_OS_WIN
|
||||
CrashDumpFile = fopen(QFile::encodeName(CrashDumpPath).constData(), "wb");
|
||||
#endif
|
||||
#endif // else for Q_OS_WIN
|
||||
if (CrashDumpFile) {
|
||||
#ifdef Q_OS_WIN
|
||||
CrashDumpFileNo = _fileno(CrashDumpFile);
|
||||
#else // Q_OS_WIN
|
||||
CrashDumpFileNo = fileno(CrashDumpFile);
|
||||
#endif // else for Q_OS_WIN
|
||||
if (SetSignalHandlers) {
|
||||
#ifndef Q_OS_WIN
|
||||
struct sigaction sigact;
|
||||
|
@ -984,12 +1028,12 @@ namespace SignalHandlers {
|
|||
sigaction(SIGFPE, &sigact, &SIG_def[SIGFPE]);
|
||||
sigaction(SIGBUS, &sigact, &SIG_def[SIGBUS]);
|
||||
sigaction(SIGSYS, &sigact, &SIG_def[SIGSYS]);
|
||||
#else
|
||||
#else // !Q_OS_WIN
|
||||
signal(SIGABRT, SignalHandlers::Handler);
|
||||
signal(SIGSEGV, SignalHandlers::Handler);
|
||||
signal(SIGILL, SignalHandlers::Handler);
|
||||
signal(SIGFPE, SignalHandlers::Handler);
|
||||
#endif
|
||||
#endif // else for !Q_OS_WIN
|
||||
}
|
||||
return Started;
|
||||
}
|
||||
|
@ -997,20 +1041,25 @@ namespace SignalHandlers {
|
|||
LOG(("FATAL: Could not open '%1' for writing!").arg(CrashDumpPath));
|
||||
|
||||
return CantOpen;
|
||||
#else // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
return Started;
|
||||
#endif // else for !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
void finish() {
|
||||
FinishBreakpad();
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
FinishCrashHandler();
|
||||
if (CrashDumpFile) {
|
||||
fclose(CrashDumpFile);
|
||||
CrashDumpFile = nullptr;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
_wunlink(CrashDumpPath.toStdWString().c_str());
|
||||
#else
|
||||
#else // Q_OS_WIN
|
||||
unlink(CrashDumpPath.toUtf8().constData());
|
||||
#endif
|
||||
#endif // else for Q_OS_WIN
|
||||
}
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
void setSelfUsername(const QString &username) {
|
||||
|
|
|
@ -30,8 +30,10 @@ int main(int argc, char *argv[]) {
|
|||
return psFixPrevious();
|
||||
} else if (cLaunchMode() == LaunchModeCleanup) {
|
||||
return psCleanup();
|
||||
#ifndef TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
} else if (cLaunchMode() == LaunchModeShowCrash) {
|
||||
return showCrashReportWindow(QFileInfo(cStartUrl()).absoluteFilePath());
|
||||
#endif // !TDESKTOP_DISABLE_CRASH_REPORTS
|
||||
}
|
||||
|
||||
// both are finished in Application::closeApplication
|
||||
|
@ -60,16 +62,18 @@ int main(int argc, char *argv[]) {
|
|||
|
||||
#ifndef TDESKTOP_DISABLE_AUTOUPDATE
|
||||
if (cRestartingUpdate()) {
|
||||
DEBUG_LOG(("Application Info: executing updater to install update.."));
|
||||
DEBUG_LOG(("Application Info: executing updater to install update..."));
|
||||
psExecUpdater();
|
||||
} else
|
||||
#endif
|
||||
if (cRestarting()) {
|
||||
DEBUG_LOG(("Application Info: executing Telegram, because of restart.."));
|
||||
DEBUG_LOG(("Application Info: executing Telegram, because of restart..."));
|
||||
psExecTelegram();
|
||||
}
|
||||
|
||||
SignalHandlers::finish();
|
||||
PlatformSpecific::finish();
|
||||
Logs::finish();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,8 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
|
||||
#include "localstorage.h"
|
||||
|
||||
#include "shortcuts.h"
|
||||
|
||||
#include "audio.h"
|
||||
|
||||
TopBarWidget::TopBarWidget(MainWidget *w) : TWidget(w)
|
||||
|
@ -56,6 +58,7 @@ TopBarWidget::TopBarWidget(MainWidget *w) : TWidget(w)
|
|||
, _addContact(this, lang(lng_profile_add_contact), st::topBarButton)
|
||||
, _deleteContact(this, lang(lng_profile_delete_contact), st::topBarButton)
|
||||
, _mediaType(this, lang(lng_media_type), st::topBarButton)
|
||||
, _search(this, st::topBarSearch)
|
||||
, _sideShadow(this, st::shadowColor) {
|
||||
|
||||
connect(&_forward, SIGNAL(clicked()), this, SLOT(onForwardSelection()));
|
||||
|
@ -66,6 +69,7 @@ TopBarWidget::TopBarWidget(MainWidget *w) : TWidget(w)
|
|||
connect(&_deleteContact, SIGNAL(clicked()), this, SLOT(onDeleteContact()));
|
||||
connect(&_edit, SIGNAL(clicked()), this, SLOT(onEdit()));
|
||||
connect(&_leaveGroup, SIGNAL(clicked()), this, SLOT(onDeleteAndExit()));
|
||||
connect(&_search, SIGNAL(clicked()), this, SLOT(onSearch()));
|
||||
|
||||
setCursor(style::cur_pointer);
|
||||
showAll();
|
||||
|
@ -147,6 +151,10 @@ void TopBarWidget::onDeleteAndExitSure() {
|
|||
}
|
||||
}
|
||||
|
||||
void TopBarWidget::onSearch() {
|
||||
Shortcuts::launch(qsl("search"));
|
||||
}
|
||||
|
||||
void TopBarWidget::enterEvent(QEvent *e) {
|
||||
a_over.start(1);
|
||||
_a_appearance.start();
|
||||
|
@ -261,6 +269,7 @@ void TopBarWidget::resizeEvent(QResizeEvent *e) {
|
|||
if (!_edit.isHidden()) _edit.move(r -= _edit.width(), 0);
|
||||
if (!_addContact.isHidden()) _addContact.move(r -= _addContact.width(), 0);
|
||||
if (!_mediaType.isHidden()) _mediaType.move(r -= _mediaType.width(), 0);
|
||||
_search.move(width() - (_info.isHidden() ? st::topBarForwardPadding.right() : _info.width()) - _search.width(), 0);
|
||||
|
||||
_sideShadow.resize(st::lineWidth, height());
|
||||
_sideShadow.moveToLeft(0, 0);
|
||||
|
@ -276,6 +285,7 @@ void TopBarWidget::startAnim() {
|
|||
_delete.hide();
|
||||
_forward.hide();
|
||||
_mediaType.hide();
|
||||
_search.hide();
|
||||
|
||||
_animating = true;
|
||||
}
|
||||
|
@ -318,6 +328,7 @@ void TopBarWidget::showAll() {
|
|||
_delete.hide();
|
||||
_forward.hide();
|
||||
_mediaType.hide();
|
||||
_search.hide();
|
||||
} else {
|
||||
if (p && p->isChannel() && (p->asChannel()->amCreator() || (p->isMegagroup() && p->asChannel()->amEditor()))) {
|
||||
_edit.show();
|
||||
|
@ -346,9 +357,15 @@ void TopBarWidget::showAll() {
|
|||
_mediaType.hide();
|
||||
}
|
||||
}
|
||||
if (App::main() && App::main()->historyPeer() && !o && !p && _clearSelection.isHidden() && Adaptive::OneColumn()) {
|
||||
_info.show();
|
||||
if (h && !o && !p && _clearSelection.isHidden()) {
|
||||
if (Adaptive::OneColumn()) {
|
||||
_info.show();
|
||||
} else {
|
||||
_info.hide();
|
||||
}
|
||||
_search.show();
|
||||
} else {
|
||||
_search.hide();
|
||||
_info.hide();
|
||||
}
|
||||
}
|
||||
|
@ -598,20 +615,21 @@ void MainWidget::finishForwarding(History *hist, bool broadcast, bool silent) {
|
|||
PeerData *forwardFrom = 0;
|
||||
App::main()->readServerHistory(hist, false);
|
||||
|
||||
int32 sendFlags = 0, flags = 0;
|
||||
MTPDmessage::Flags flags = 0;
|
||||
MTPmessages_ForwardMessages::Flags sendFlags = 0;
|
||||
bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup() && hist->peer->asChannel()->canPublish() && (hist->peer->asChannel()->isBroadcast() || broadcast);
|
||||
bool showFromName = !channelPost || hist->peer->asChannel()->addsSignature();
|
||||
bool silentPost = channelPost && silent;
|
||||
if (channelPost) {
|
||||
sendFlags |= MTPmessages_ForwardMessages::flag_broadcast;
|
||||
flags |= MTPDmessage::flag_views;
|
||||
flags |= MTPDmessage::flag_post;
|
||||
sendFlags |= MTPmessages_ForwardMessages::Flag::f_broadcast;
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
flags |= MTPDmessage::Flag::f_post;
|
||||
}
|
||||
if (showFromName) {
|
||||
flags |= MTPDmessage::flag_from_id;
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
}
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_ForwardMessages::flag_silent;
|
||||
sendFlags |= MTPmessages_ForwardMessages::Flag::f_silent;
|
||||
}
|
||||
|
||||
QVector<MTPint> ids;
|
||||
|
@ -619,7 +637,7 @@ void MainWidget::finishForwarding(History *hist, bool broadcast, bool silent) {
|
|||
ids.reserve(_toForward.size());
|
||||
randomIds.reserve(_toForward.size());
|
||||
for (SelectedItemSet::const_iterator i = _toForward.cbegin(), e = _toForward.cend(); i != e; ++i) {
|
||||
uint64 randomId = MTP::nonce<uint64>();
|
||||
uint64 randomId = rand_value<uint64>();
|
||||
if (genClientSideMessage) {
|
||||
FullMsgId newId(peerToChannel(hist->peer->id), clientMsgId());
|
||||
HistoryMessage *msg = static_cast<HistoryMessage*>(_toForward.cbegin().value());
|
||||
|
@ -633,7 +651,7 @@ void MainWidget::finishForwarding(History *hist, bool broadcast, bool silent) {
|
|||
}
|
||||
if (forwardFrom != i.value()->history()->peer) {
|
||||
if (forwardFrom) {
|
||||
hist->sendRequestId = MTP::send(MTPmessages_ForwardMessages(MTP_int(sendFlags), forwardFrom->input, MTP_vector<MTPint>(ids), MTP_vector<MTPlong>(randomIds), hist->peer->input), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
|
||||
hist->sendRequestId = MTP::send(MTPmessages_ForwardMessages(MTP_flags(sendFlags), forwardFrom->input, MTP_vector<MTPint>(ids), MTP_vector<MTPlong>(randomIds), hist->peer->input), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
|
||||
ids.resize(0);
|
||||
randomIds.resize(0);
|
||||
}
|
||||
|
@ -642,7 +660,7 @@ void MainWidget::finishForwarding(History *hist, bool broadcast, bool silent) {
|
|||
ids.push_back(MTP_int(i.value()->id));
|
||||
randomIds.push_back(MTP_long(randomId));
|
||||
}
|
||||
hist->sendRequestId = MTP::send(MTPmessages_ForwardMessages(MTP_int(sendFlags), forwardFrom->input, MTP_vector<MTPint>(ids), MTP_vector<MTPlong>(randomIds), hist->peer->input), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
|
||||
hist->sendRequestId = MTP::send(MTPmessages_ForwardMessages(MTP_flags(sendFlags), forwardFrom->input, MTP_vector<MTPint>(ids), MTP_vector<MTPlong>(randomIds), hist->peer->input), rpcDone(&MainWidget::sentUpdatesReceived), RPCFailHandlerPtr(), 0, 0, hist->sendRequestId);
|
||||
|
||||
if (history.peer() == hist->peer) {
|
||||
history.peerMessagesUpdated();
|
||||
|
@ -670,8 +688,7 @@ void MainWidget::webPagesUpdate() {
|
|||
WebPageItems::const_iterator j = items.constFind(App::webPage(i.key()));
|
||||
if (j != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator k = j.value().cbegin(), e = j.value().cend(); k != e; ++k) {
|
||||
k.key()->initDimensions();
|
||||
Notify::historyItemResized(k.key());
|
||||
k.key()->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -766,8 +783,7 @@ void MainWidget::notify_userIsContactChanged(UserData *user, bool fromThisApp) {
|
|||
SharedContactItems::const_iterator i = items.constFind(peerToUser(user->id));
|
||||
if (i != items.cend()) {
|
||||
for (HistoryItemsMap::const_iterator j = i->cbegin(), e = i->cend(); j != e; ++j) {
|
||||
j.key()->initDimensions();
|
||||
Ui::repaintHistoryItem(j.key());
|
||||
j.key()->setPendingInitDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -813,6 +829,10 @@ void MainWidget::notify_automaticLoadSettingsChangedGif() {
|
|||
history.notify_automaticLoadSettingsChangedGif();
|
||||
}
|
||||
|
||||
void MainWidget::notify_handlePendingHistoryUpdate() {
|
||||
history.notify_handlePendingHistoryUpdate();
|
||||
}
|
||||
|
||||
void MainWidget::cmd_search() {
|
||||
history.cmd_search();
|
||||
}
|
||||
|
@ -825,18 +845,6 @@ void MainWidget::cmd_previous_chat() {
|
|||
history.cmd_previous_chat();
|
||||
}
|
||||
|
||||
void MainWidget::notify_historyItemResized(const HistoryItem *item, bool scrollToIt) {
|
||||
if (!item || ((history.peer() == item->history()->peer || (history.peer() && history.peer() == item->history()->peer->migrateTo())) && !item->detached())) {
|
||||
history.notify_historyItemResized(item, scrollToIt);
|
||||
} else if (item) {
|
||||
item->history()->width = 0;
|
||||
if (history.peer() == item->history()->peer || (history.peer() && history.peer() == item->history()->peer->migrateTo())) {
|
||||
history.resizeEvent(0);
|
||||
}
|
||||
}
|
||||
if (item) Ui::repaintHistoryItem(item);
|
||||
}
|
||||
|
||||
void MainWidget::noHider(HistoryHider *destroyed) {
|
||||
if (_hider == destroyed) {
|
||||
_hider = 0;
|
||||
|
@ -909,7 +917,7 @@ void MainWidget::forwardLayer(int32 forwardSelected) {
|
|||
|
||||
void MainWidget::deleteLayer(int32 selectedCount) {
|
||||
if (selectedCount == -1 && !overview) {
|
||||
if (auto item = App::contextItem()) {
|
||||
if (HistoryItem *item = App::contextItem()) {
|
||||
if (item->suggestBanReportDeleteAll()) {
|
||||
Ui::showLayer(new RichDeleteMessageBox(item->history()->peer->asChannel(), item->from()->asUser(), item->id));
|
||||
return;
|
||||
|
@ -1067,16 +1075,16 @@ void MainWidget::deleteAllFromUser(ChannelData *channel, UserData *from) {
|
|||
t_assert(channel != nullptr && from != nullptr);
|
||||
|
||||
QVector<MsgId> toDestroy;
|
||||
if (auto history = App::historyLoaded(channel->id)) {
|
||||
for (auto i = history->blocks.cbegin(), e = history->blocks.cend(); i != e; ++i) {
|
||||
for (auto j = (*i)->items.cbegin(), n = (*i)->items.cend(); j != n; ++j) {
|
||||
if ((*j)->from() == from && (*j)->type() == HistoryItemMsg && (*j)->canDelete()) {
|
||||
toDestroy.push_back((*j)->id);
|
||||
if (History *history = App::historyLoaded(channel->id)) {
|
||||
for (HistoryBlock *block : history->blocks) {
|
||||
for (HistoryItem *item : block->items) {
|
||||
if (item->from() == from && item->type() == HistoryItemMsg && item->canDelete()) {
|
||||
toDestroy.push_back(item->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto i = toDestroy.cbegin(), e = toDestroy.cend(); i != e; ++i) {
|
||||
if (auto item = App::histItemById(peerToChannel(channel->id), *i)) {
|
||||
for (const MsgId &msgId : toDestroy) {
|
||||
if (HistoryItem *item = App::histItemById(peerToChannel(channel->id), msgId)) {
|
||||
item->destroy();
|
||||
}
|
||||
}
|
||||
|
@ -1095,7 +1103,7 @@ void MainWidget::deleteAllFromUserPart(DeleteAllFromUserParams params, const MTP
|
|||
if (!MTP::authedId()) return;
|
||||
if (offset > 0) {
|
||||
MTP::send(MTPchannels_DeleteUserHistory(params.channel->inputChannel, params.from->inputUser), rpcDone(&MainWidget::deleteAllFromUserPart, params));
|
||||
} else if (auto h = App::historyLoaded(params.channel)) {
|
||||
} else if (History *h = App::historyLoaded(params.channel)) {
|
||||
if (!h->lastMsg) {
|
||||
checkPeerHistory(params.channel);
|
||||
}
|
||||
|
@ -1139,7 +1147,9 @@ bool MainWidget::addParticipantFail(UserData *user, const RPCError &error) {
|
|||
if (mtpIsFlood(error)) return false;
|
||||
|
||||
QString text = lang(lng_failed_add_participant);
|
||||
if (error.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group
|
||||
if (error.type() == "USER_LEFT_CHAT") { // trying to return a user who has left
|
||||
} else if (error.type() == "USER_KICKED") { // trying to return a user who was kicked by admin
|
||||
text = lang(lng_cant_invite_banned);
|
||||
} else if (error.type() == "USER_PRIVACY_RESTRICTED") {
|
||||
text = lang(lng_cant_invite_privacy);
|
||||
} else if (error.type() == "USER_NOT_MUTUAL_CONTACT") { // trying to return user who does not have me in contacts
|
||||
|
@ -1158,8 +1168,10 @@ bool MainWidget::addParticipantsFail(ChannelData *channel, const RPCError &error
|
|||
|
||||
QString text = lang(lng_failed_add_participant);
|
||||
if (error.type() == "USER_LEFT_CHAT") { // trying to return banned user to his group
|
||||
} else if (error.type() == "USER_KICKED") { // trying to return a user who was kicked by admin
|
||||
text = lang(lng_cant_invite_banned);
|
||||
} else if (error.type() == "USER_PRIVACY_RESTRICTED") {
|
||||
text = lang(lng_cant_invite_privacy_channel);
|
||||
text = lang(channel->isMegagroup() ? lng_cant_invite_privacy : lng_cant_invite_privacy_channel);
|
||||
} else if (error.type() == "USER_NOT_MUTUAL_CONTACT") { // trying to return user who does not have me in contacts
|
||||
text = lang(channel->isMegagroup() ? lng_failed_add_not_mutual : lng_failed_add_not_mutual_channel);
|
||||
} else if (error.type() == "PEER_FLOOD") {
|
||||
|
@ -1354,7 +1366,7 @@ void MainWidget::sendMessage(History *hist, const QString &text, MsgId replyTo,
|
|||
if (replyTo < 0) replyTo = history.replyToId();
|
||||
while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
|
||||
FullMsgId newId(peerToChannel(hist->peer->id), clientMsgId());
|
||||
uint64 randomId = MTP::nonce<uint64>();
|
||||
uint64 randomId = rand_value<uint64>();
|
||||
|
||||
trimTextWithEntities(sendingText, sendingEntities);
|
||||
|
||||
|
@ -1362,40 +1374,40 @@ void MainWidget::sendMessage(History *hist, const QString &text, MsgId replyTo,
|
|||
App::historyRegSentData(randomId, hist->peer->id, sendingText);
|
||||
|
||||
MTPstring msgText(MTP_string(sendingText));
|
||||
int32 flags = newMessageFlags(hist->peer) | MTPDmessage::flag_entities; // unread, out
|
||||
int32 sendFlags = 0;
|
||||
MTPDmessage::Flags flags = newMessageFlags(hist->peer) | MTPDmessage::Flag::f_entities; // unread, out
|
||||
MTPmessages_SendMessage::Flags sendFlags = 0;
|
||||
if (replyTo) {
|
||||
flags |= MTPDmessage::flag_reply_to_msg_id;
|
||||
sendFlags |= MTPmessages_SendMessage::flag_reply_to_msg_id;
|
||||
flags |= MTPDmessage::Flag::f_reply_to_msg_id;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_reply_to_msg_id;
|
||||
}
|
||||
MTPMessageMedia media = MTP_messageMediaEmpty();
|
||||
if (webPageId == CancelledWebPageId) {
|
||||
sendFlags |= MTPmessages_SendMessage::flag_no_webpage;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_no_webpage;
|
||||
} else if (webPageId) {
|
||||
WebPageData *page = App::webPage(webPageId);
|
||||
media = MTP_messageMediaWebPage(MTP_webPagePending(MTP_long(page->id), MTP_int(page->pendingTill)));
|
||||
flags |= MTPDmessage::flag_media;
|
||||
flags |= MTPDmessage::Flag::f_media;
|
||||
}
|
||||
bool channelPost = hist->peer->isChannel() && !hist->peer->isMegagroup() && hist->peer->asChannel()->canPublish() && (hist->peer->asChannel()->isBroadcast() || broadcast);
|
||||
bool showFromName = !channelPost || hist->peer->asChannel()->addsSignature();
|
||||
bool silentPost = channelPost && silent;
|
||||
if (channelPost) {
|
||||
sendFlags |= MTPmessages_SendMessage::flag_broadcast;
|
||||
flags |= MTPDmessage::flag_views;
|
||||
flags |= MTPDmessage::flag_post;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_broadcast;
|
||||
flags |= MTPDmessage::Flag::f_views;
|
||||
flags |= MTPDmessage::Flag::f_post;
|
||||
}
|
||||
if (showFromName) {
|
||||
flags |= MTPDmessage::flag_from_id;
|
||||
flags |= MTPDmessage::Flag::f_from_id;
|
||||
}
|
||||
if (silentPost) {
|
||||
sendFlags |= MTPmessages_SendMessage::flag_silent;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_silent;
|
||||
}
|
||||
MTPVector<MTPMessageEntity> localEntities = linksToMTP(sendingEntities), sentEntities = linksToMTP(sendingEntities, true);
|
||||
if (!sentEntities.c_vector().v.isEmpty()) {
|
||||
sendFlags |= MTPmessages_SendMessage::flag_entities;
|
||||
sendFlags |= MTPmessages_SendMessage::Flag::f_entities;
|
||||
}
|
||||
hist->addNewMessage(MTP_message(MTP_int(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(hist->peer->id), MTPnullFwdHeader, MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1), MTPint()), NewMessageUnread);
|
||||
hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_int(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, sentEntities), rpcDone(&MainWidget::sentUpdatesReceived, randomId), rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
|
||||
hist->addNewMessage(MTP_message(MTP_flags(flags), MTP_int(newId.msg), MTP_int(showFromName ? MTP::authedId() : 0), peerToMTP(hist->peer->id), MTPnullFwdHeader, MTPint(), MTP_int(replyTo), MTP_int(unixtime()), msgText, media, MTPnullMarkup, localEntities, MTP_int(1), MTPint()), NewMessageUnread);
|
||||
hist->sendRequestId = MTP::send(MTPmessages_SendMessage(MTP_flags(sendFlags), hist->peer->input, MTP_int(replyTo), msgText, MTP_long(randomId), MTPnullMarkup, sentEntities), rpcDone(&MainWidget::sentUpdatesReceived, randomId), rpcFail(&MainWidget::sendMessageFail), 0, 0, hist->sendRequestId);
|
||||
}
|
||||
|
||||
finishForwarding(hist, broadcast, silent);
|
||||
|
@ -1485,8 +1497,11 @@ bool MainWidget::preloadOverview(PeerData *peer, MediaOverviewType type) {
|
|||
return false;
|
||||
}
|
||||
|
||||
int32 flags = (peer->isChannel() && !peer->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0;
|
||||
_overviewPreload[type].insert(peer, MTP::send(MTPmessages_Search(MTP_int(flags), peer->input, MTP_string(""), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0)), rpcDone(&MainWidget::overviewPreloaded, peer), rpcFail(&MainWidget::overviewFailed, peer), 0, 10));
|
||||
MTPmessages_Search::Flags flags = 0;
|
||||
if (peer->isChannel() && !peer->isMegagroup()) {
|
||||
flags |= MTPmessages_Search::Flag::f_important_only;
|
||||
}
|
||||
_overviewPreload[type].insert(peer, MTP::send(MTPmessages_Search(MTP_flags(flags), peer->input, MTP_string(""), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(0)), rpcDone(&MainWidget::overviewPreloaded, peer), rpcFail(&MainWidget::overviewFailed, peer), 0, 10));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1618,8 +1633,11 @@ void MainWidget::loadMediaBack(PeerData *peer, MediaOverviewType type, bool many
|
|||
MTPMessagesFilter filter = typeToMediaFilter(type);
|
||||
if (type == OverviewCount) return;
|
||||
|
||||
int32 flags = (peer->isChannel() && !peer->isMegagroup()) ? MTPmessages_Search::flag_important_only : 0;
|
||||
_overviewLoad[type].insert(peer, MTP::send(MTPmessages_Search(MTP_int(flags), peer->input, MTPstring(), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(minId), MTP_int(limit)), rpcDone(&MainWidget::overviewLoaded, history)));
|
||||
MTPmessages_Search::Flags flags = 0;
|
||||
if (peer->isChannel() && !peer->isMegagroup()) {
|
||||
flags |= MTPmessages_Search::Flag::f_important_only;
|
||||
}
|
||||
_overviewLoad[type].insert(peer, MTP::send(MTPmessages_Search(MTP_flags(flags), peer->input, MTPstring(), filter, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(minId), MTP_int(limit)), rpcDone(&MainWidget::overviewLoaded, history)));
|
||||
}
|
||||
|
||||
void MainWidget::peerUsernameChanged(PeerData *peer) {
|
||||
|
@ -1745,6 +1763,14 @@ void MainWidget::ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId) {
|
|||
Ui::showPeerHistory(peerId, showAtMsgId);
|
||||
}
|
||||
|
||||
void MainWidget::ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId) {
|
||||
if (HistoryItem *item = App::histItemById(channelId, msgId)) {
|
||||
if (HistoryMedia *media = item->getMedia()) {
|
||||
media->playInline(item, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWidget::audioPlayProgress(const AudioMsgId &audioId) {
|
||||
AudioMsgId playing;
|
||||
AudioPlayerState state = AudioPlayerStopped;
|
||||
|
@ -1753,24 +1779,9 @@ void MainWidget::audioPlayProgress(const AudioMsgId &audioId) {
|
|||
audioPlayer()->clearStoppedAtStart(audioId);
|
||||
|
||||
DocumentData *audio = audioId.audio;
|
||||
QString already = audio->already(true);
|
||||
if (already.isEmpty() && !audio->data().isEmpty()) {
|
||||
bool mp3 = (audio->mime == qstr("audio/mp3"));
|
||||
QString filename = saveFileName(lang(lng_save_audio), mp3 ? qsl("MP3 Audio (*.mp3);;All files (*.*)") : qsl("OGG Opus Audio (*.ogg);;All files (*.*)"), qsl("audio"), mp3 ? qsl(".mp3") : qsl(".ogg"), false);
|
||||
if (!filename.isEmpty()) {
|
||||
QFile f(filename);
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
if (f.write(audio->data()) == audio->data().size()) {
|
||||
f.close();
|
||||
already = filename;
|
||||
audio->setLocation(FileLocation(StorageFilePartial, filename));
|
||||
Local::writeFileLocation(mediaKey(AudioFileLocation, audio->dc, audio->id), FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!already.isEmpty()) {
|
||||
psOpenFile(already);
|
||||
QString filepath = audio->filepath(DocumentData::FilePathResolveSaveFromData);
|
||||
if (!filepath.isEmpty()) {
|
||||
psOpenFile(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1790,35 +1801,9 @@ void MainWidget::documentPlayProgress(const SongMsgId &songId) {
|
|||
audioPlayer()->clearStoppedAtStart(songId);
|
||||
|
||||
DocumentData *document = songId.song;
|
||||
QString already = document->already(true);
|
||||
if (already.isEmpty() && !document->data().isEmpty()) {
|
||||
QString name = document->name, filter;
|
||||
MimeType mimeType = mimeTypeForName(document->mime);
|
||||
QStringList p = mimeType.globPatterns();
|
||||
QString pattern = p.isEmpty() ? QString() : p.front();
|
||||
if (name.isEmpty()) {
|
||||
name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString());
|
||||
}
|
||||
if (pattern.isEmpty()) {
|
||||
filter = QString();
|
||||
} else {
|
||||
filter = mimeType.filterString() + qsl(";;All files (*.*)");
|
||||
}
|
||||
QString filename = saveFileName(lang(lng_save_file), filter, qsl("doc"), name, false);
|
||||
if (!filename.isEmpty()) {
|
||||
QFile f(filename);
|
||||
if (f.open(QIODevice::WriteOnly)) {
|
||||
if (f.write(document->data()) == document->data().size()) {
|
||||
f.close();
|
||||
already = filename;
|
||||
document->setLocation(FileLocation(StorageFilePartial, filename));
|
||||
Local::writeFileLocation(mediaKey(DocumentFileLocation, document->dc, document->id), FileLocation(mtpToStorageType(mtpc_storage_filePartial), filename));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!already.isEmpty()) {
|
||||
psOpenFile(already);
|
||||
QString filepath = document->filepath(DocumentData::FilePathResolveSaveFromData);
|
||||
if (!filepath.isEmpty()) {
|
||||
psOpenFile(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1977,13 +1962,13 @@ void MainWidget::dialogsCancelled() {
|
|||
}
|
||||
|
||||
void MainWidget::serviceNotification(const QString &msg, const MTPMessageMedia &media) {
|
||||
int32 flags = MTPDmessage::flag_unread | MTPDmessage::flag_entities | MTPDmessage::flag_from_id;
|
||||
MTPDmessage::Flags flags = MTPDmessage::Flag::f_unread | MTPDmessage::Flag::f_entities | MTPDmessage::Flag::f_from_id;
|
||||
QString sendingText, leftText = msg;
|
||||
EntitiesInText sendingEntities, leftEntities = textParseEntities(leftText, _historyTextNoMonoOptions.flags);
|
||||
HistoryItem *item = 0;
|
||||
while (textSplit(sendingText, sendingEntities, leftText, leftEntities, MaxMessageSize)) {
|
||||
MTPVector<MTPMessageEntity> localEntities = linksToMTP(sendingEntities);
|
||||
item = App::histories().addNewMessage(MTP_message(MTP_int(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(MTP::authedId())), MTPnullFwdHeader, MTPint(), MTPint(), MTP_int(unixtime()), MTP_string(sendingText), media, MTPnullMarkup, localEntities, MTPint(), MTPint()), NewMessageUnread);
|
||||
item = App::histories().addNewMessage(MTP_message(MTP_flags(flags), MTP_int(clientMsgId()), MTP_int(ServiceUserId), MTP_peerUser(MTP_int(MTP::authedId())), MTPnullFwdHeader, MTPint(), MTPint(), MTP_int(unixtime()), MTP_string(sendingText), media, MTPnullMarkup, localEntities, MTPint(), MTPint()), NewMessageUnread);
|
||||
}
|
||||
if (item) {
|
||||
history.peerMessagesUpdated(item->history()->peer->id);
|
||||
|
@ -2263,9 +2248,16 @@ void MainWidget::ctrlEnterSubmitUpdated() {
|
|||
void MainWidget::ui_showPeerHistory(quint64 peerId, qint32 showAtMsgId, bool back) {
|
||||
if (PeerData *peer = App::peerLoaded(peerId)) {
|
||||
if (peer->migrateTo()) {
|
||||
peerId = peer->migrateTo()->id;
|
||||
peer = peer->migrateTo();
|
||||
peerId = peer->id;
|
||||
if (showAtMsgId > 0) showAtMsgId = -showAtMsgId;
|
||||
}
|
||||
QString restriction = peer->restrictionReason();
|
||||
if (!restriction.isEmpty()) {
|
||||
Ui::showChatsList();
|
||||
Ui::showLayer(new InformBox(restriction));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!back && (!peerId || (_stack.size() == 1 && _stack[0]->type() == HistoryStackItem && _stack[0]->peer->id == peerId))) {
|
||||
back = true;
|
||||
|
@ -2607,6 +2599,16 @@ void MainWidget::sentUpdatesReceived(uint64 randomId, const MTPUpdates &result)
|
|||
App::emitPeerUpdated();
|
||||
}
|
||||
|
||||
bool MainWidget::deleteChannelFailed(const RPCError &error) {
|
||||
if (mtpIsFlood(error)) return false;
|
||||
|
||||
//if (error.type() == qstr("CHANNEL_TOO_LARGE")) {
|
||||
// Ui::showLayer(new InformBox(lang(lng_cant_delete_channel)));
|
||||
//}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MainWidget::inviteToChannelDone(ChannelData *channel, const MTPUpdates &updates) {
|
||||
sentUpdatesReceived(updates);
|
||||
QTimer::singleShot(ReloadChannelMembersTimeout, this, SLOT(onActiveChannelUpdateFull()));
|
||||
|
@ -2934,7 +2936,7 @@ void MainWidget::onUpdateNotifySettings() {
|
|||
if (peer->notify == UnknownNotifySettings || peer->notify == EmptyNotifySettings) {
|
||||
peer->notify = new NotifySettings();
|
||||
}
|
||||
MTP::send(MTPaccount_UpdateNotifySettings(MTP_inputNotifyPeer(peer->input), MTP_inputPeerNotifySettings(MTP_int(peer->notify->flags), MTP_int(peer->notify->mute), MTP_string(peer->notify->sound))), RPCResponseHandler(), 0, updateNotifySettingPeers.isEmpty() ? 0 : 10);
|
||||
MTP::send(MTPaccount_UpdateNotifySettings(MTP_inputNotifyPeer(peer->input), MTP_inputPeerNotifySettings(MTP_flags(mtpCastFlags(peer->notify->flags)), MTP_int(peer->notify->mute), MTP_string(peer->notify->sound))), RPCResponseHandler(), 0, updateNotifySettingPeers.isEmpty() ? 0 : 10);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3031,7 +3033,7 @@ void MainWidget::gotChannelDifference(ChannelData *channel, const MTPupdates_Cha
|
|||
}
|
||||
if (history.peer() == channel) {
|
||||
history.updateToEndVisibility();
|
||||
history.onListScroll();
|
||||
history.preloadHistoryIfNeeded();
|
||||
}
|
||||
h->asChannelHistory()->getRangeDifference();
|
||||
}
|
||||
|
@ -3431,8 +3433,7 @@ void MainWidget::getChannelDifference(ChannelData *channel, GetChannelDifference
|
|||
int32 fixInScrollMsgTop = 0;
|
||||
history->asChannelHistory()->getSwitchReadyFor(SwitchAtTopMsgId, fixInScrollMsgId, fixInScrollMsgTop);
|
||||
history->getReadyFor(ShowAtTheEndMsgId, fixInScrollMsgId, fixInScrollMsgTop);
|
||||
history->lastWidth = 0;
|
||||
history->lastScrollTop = INT_MAX;
|
||||
history->forgetScrollState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3823,9 +3824,9 @@ void MainWidget::updateNotifySetting(PeerData *peer, NotifySettingStatus notify,
|
|||
peer->notify->mute = (notify == NotifySettingSetMuted) ? (unixtime() + muteFor) : 0;
|
||||
}
|
||||
if (silent == SilentNotifiesSetSilent) {
|
||||
peer->notify->flags |= MTPDpeerNotifySettings::flag_silent;
|
||||
peer->notify->flags |= MTPDpeerNotifySettings::Flag::f_silent;
|
||||
} else if (silent == SilentNotifiesSetNotify) {
|
||||
peer->notify->flags &= ~MTPDpeerNotifySettings::flag_silent;
|
||||
peer->notify->flags &= ~MTPDpeerNotifySettings::Flag::f_silent;
|
||||
}
|
||||
}
|
||||
if (notify != NotifySettingDontChange) {
|
||||
|
@ -3888,9 +3889,9 @@ void MainWidget::incrementSticker(DocumentData *sticker) {
|
|||
case mtpc_inputStickerSetID: setId = sticker->sticker()->set.c_inputStickerSetID().vid.v; break;
|
||||
case mtpc_inputStickerSetShortName: setName = qs(sticker->sticker()->set.c_inputStickerSetShortName().vshort_name).toLower().trimmed(); break;
|
||||
}
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
for (StickerSets::const_iterator i = sets.cbegin(); i != sets.cend(); ++i) {
|
||||
if (i->id == CustomStickerSetId || i->id == DefaultStickerSetId || (setId && i->id == setId) || (!setName.isEmpty() && i->shortName.toLower().trimmed() == setName)) {
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
for (auto i = sets.cbegin(); i != sets.cend(); ++i) {
|
||||
if (i->id == Stickers::CustomSetId || i->id == Stickers::DefaultSetId || (setId && i->id == setId) || (!setName.isEmpty() && i->shortName.toLower().trimmed() == setName)) {
|
||||
for (int32 j = 0, l = i->stickers.size(); j < l; ++j) {
|
||||
if (i->stickers.at(j) == sticker) {
|
||||
found = true;
|
||||
|
@ -3901,9 +3902,9 @@ void MainWidget::incrementSticker(DocumentData *sticker) {
|
|||
}
|
||||
}
|
||||
if (!found) {
|
||||
StickerSets::iterator it = sets.find(CustomStickerSetId);
|
||||
Stickers::Sets::iterator it = sets.find(Stickers::CustomSetId);
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(CustomStickerSetId, StickerSet(CustomStickerSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0));
|
||||
it = sets.insert(Stickers::CustomSetId, Stickers::Set(Stickers::CustomSetId, 0, lang(lng_custom_stickers), QString(), 0, 0, 0));
|
||||
}
|
||||
it->stickers.push_back(sticker);
|
||||
++it->count;
|
||||
|
@ -4046,7 +4047,7 @@ void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) {
|
|||
feedUpdates(updates);
|
||||
}
|
||||
App::emitPeerUpdated();
|
||||
} catch (mtpErrorUnexpected &e) { // just some other type
|
||||
} catch (mtpErrorUnexpected &) { // just some other type
|
||||
}
|
||||
}
|
||||
update();
|
||||
|
@ -4098,17 +4099,17 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
|
|||
|
||||
case mtpc_updateShortMessage: {
|
||||
const MTPDupdateShortMessage &d(updates.c_updateShortMessage());
|
||||
if (!App::userLoaded(d.vuser_id.v) || (d.has_via_bot_id() && !App::peerLoaded(peerFromUser(d.vvia_bot_id)))) {
|
||||
if (!App::userLoaded(d.vuser_id.v) || (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v))) {
|
||||
MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
|
||||
return getDifference();
|
||||
}
|
||||
if (d.has_fwd_from() && d.vfwd_from.type() == mtpc_messageFwdHeader) {
|
||||
const MTPDmessageFwdHeader &f(d.vfwd_from.c_messageFwdHeader());
|
||||
if (f.has_from_id() && !App::peerLoaded(peerFromUser(f.vfrom_id))) {
|
||||
if (f.has_from_id() && !App::userLoaded(f.vfrom_id.v)) {
|
||||
MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
|
||||
return getDifference();
|
||||
}
|
||||
if (f.has_channel_id() && !App::peerLoaded(peerFromChannel(f.vchannel_id))) {
|
||||
if (f.has_channel_id() && !App::channelLoaded(f.vchannel_id.v)) {
|
||||
MTP_LOG(0, ("getDifference { good - getting user for updateShortMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
|
||||
return getDifference();
|
||||
}
|
||||
|
@ -4118,8 +4119,8 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
|
|||
}
|
||||
|
||||
// update before applying skipped
|
||||
int32 flags = d.vflags.v | MTPDmessage::flag_from_id;
|
||||
HistoryItem *item = App::histories().addNewMessage(MTP_message(MTP_int(flags), d.vid, d.is_out() ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint(), MTPint()), NewMessageUnread);
|
||||
MTPDmessage::Flags flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
|
||||
HistoryItem *item = App::histories().addNewMessage(MTP_message(MTP_flags(flags), d.vid, d.is_out() ? MTP_int(MTP::authedId()) : d.vuser_id, MTP_peerUser(d.is_out() ? d.vuser_id : MTP_int(MTP::authedId())), d.vfwd_from, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint(), MTPint()), NewMessageUnread);
|
||||
if (item) {
|
||||
history.peerMessagesUpdated(item->history()->peer->id);
|
||||
}
|
||||
|
@ -4132,18 +4133,18 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
|
|||
case mtpc_updateShortChatMessage: {
|
||||
const MTPDupdateShortChatMessage &d(updates.c_updateShortChatMessage());
|
||||
bool noFrom = !App::userLoaded(d.vfrom_id.v);
|
||||
if (!App::chatLoaded(d.vchat_id.v) || noFrom || (d.has_via_bot_id() && !App::peerLoaded(peerFromUser(d.vvia_bot_id)))) {
|
||||
if (!App::chatLoaded(d.vchat_id.v) || noFrom || (d.has_via_bot_id() && !App::userLoaded(d.vvia_bot_id.v))) {
|
||||
MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
|
||||
if (noFrom && App::api()) App::api()->requestFullPeer(App::chatLoaded(d.vchat_id.v));
|
||||
return getDifference();
|
||||
}
|
||||
if (d.has_fwd_from() && d.vfwd_from.type() == mtpc_messageFwdHeader) {
|
||||
const MTPDmessageFwdHeader &f(d.vfwd_from.c_messageFwdHeader());
|
||||
if (f.has_from_id() && !App::peerLoaded(peerFromUser(f.vfrom_id))) {
|
||||
if (f.has_from_id() && !App::userLoaded(f.vfrom_id.v)) {
|
||||
MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
|
||||
return getDifference();
|
||||
}
|
||||
if (f.has_channel_id() && !App::peerLoaded(peerFromChannel(f.vchannel_id))) {
|
||||
if (f.has_channel_id() && !App::channelLoaded(f.vchannel_id.v)) {
|
||||
MTP_LOG(0, ("getDifference { good - getting user for updateShortChatMessage }%1").arg(cTestMode() ? " TESTMODE" : ""));
|
||||
return getDifference();
|
||||
}
|
||||
|
@ -4153,8 +4154,8 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
|
|||
}
|
||||
|
||||
// update before applying skipped
|
||||
int32 flags = d.vflags.v | MTPDmessage::flag_from_id;
|
||||
HistoryItem *item = App::histories().addNewMessage(MTP_message(MTP_int(flags), d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint(), MTPint()), NewMessageUnread);
|
||||
MTPDmessage::Flags flags = mtpCastFlags(d.vflags.v) | MTPDmessage::Flag::f_from_id;
|
||||
HistoryItem *item = App::histories().addNewMessage(MTP_message(MTP_flags(flags), d.vid, d.vfrom_id, MTP_peerChat(d.vchat_id), d.vfwd_from, d.vvia_bot_id, d.vreply_to_msg_id, d.vdate, d.vmessage, MTP_messageMediaEmpty(), MTPnullMarkup, d.has_entities() ? d.ventities : MTPnullEntities, MTPint(), MTPint()), NewMessageUnread);
|
||||
if (item) {
|
||||
history.peerMessagesUpdated(item->history()->peer->id);
|
||||
}
|
||||
|
@ -4176,9 +4177,6 @@ void MainWidget::feedUpdates(const MTPUpdates &updates, uint64 randomId) {
|
|||
if (HistoryItem *item = App::histItemById(peerToChannel(peerId), d.vid.v)) {
|
||||
item->setText(text, d.has_entities() ? entitiesFromMTP(d.ventities.c_vector().v) : EntitiesInText());
|
||||
item->updateMedia(d.has_media() ? (&d.vmedia) : 0);
|
||||
item->initDimensions();
|
||||
Notify::historyItemResized(item);
|
||||
|
||||
item->addToOverview(AddToOverviewNew);
|
||||
}
|
||||
}
|
||||
|
@ -4359,9 +4357,9 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
case mtpc_updateChatUserTyping: {
|
||||
const MTPDupdateChatUserTyping &d(update.c_updateChatUserTyping());
|
||||
History *history = 0;
|
||||
if (PeerData *chat = App::peerLoaded(peerFromChat(d.vchat_id.v))) {
|
||||
if (PeerData *chat = App::chatLoaded(d.vchat_id.v)) {
|
||||
history = App::historyLoaded(chat->id);
|
||||
} else if (PeerData *channel = App::peerLoaded(peerFromChannel(d.vchat_id.v))) {
|
||||
} else if (PeerData *channel = App::channelLoaded(d.vchat_id.v)) {
|
||||
history = App::historyLoaded(channel->id);
|
||||
}
|
||||
UserData *user = (d.vuser_id.v == MTP::authedId()) ? 0 : App::userLoaded(d.vuser_id.v);
|
||||
|
@ -4438,7 +4436,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
UserData *user = App::userLoaded(d.vuser_id.v);
|
||||
if (user) {
|
||||
user->setPhoto(d.vphoto);
|
||||
user->photo->load();
|
||||
user->loadUserpic();
|
||||
if (mtpIsTrue(d.vprevious)) {
|
||||
user->photosCount = -1;
|
||||
user->photos.clear();
|
||||
|
@ -4461,7 +4459,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
UserData *user = App::userLoaded(d.vuser_id.v);
|
||||
if (user) {
|
||||
if (App::history(user->id)->loadedAtBottom()) {
|
||||
App::history(user->id)->addNewService(clientMsgId(), date(d.vdate), lng_action_user_registered(lt_from, user->name), MTPDmessage::flag_unread);
|
||||
App::history(user->id)->addNewService(clientMsgId(), date(d.vdate), lng_action_user_registered(lt_from, user->name), MTPDmessage::Flag::f_unread);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
@ -4684,10 +4682,10 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
if (set.vset.type() == mtpc_stickerSet) {
|
||||
const MTPDstickerSet &s(set.vset.c_stickerSet());
|
||||
|
||||
StickerSets &sets(cRefStickerSets());
|
||||
StickerSets::iterator it = sets.find(s.vid.v);
|
||||
Stickers::Sets &sets(Global::RefStickerSets());
|
||||
auto it = sets.find(s.vid.v);
|
||||
if (it == sets.cend()) {
|
||||
it = sets.insert(s.vid.v, StickerSet(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v));
|
||||
it = sets.insert(s.vid.v, Stickers::Set(s.vid.v, s.vaccess_hash.v, stickerSetTitle(s), qs(s.vshort_name), s.vcount.v, s.vhash.v, s.vflags.v));
|
||||
}
|
||||
|
||||
const QVector<MTPDocument> &v(set.vdocuments.c_vector().v);
|
||||
|
@ -4718,7 +4716,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
}
|
||||
}
|
||||
|
||||
StickerSetsOrder &order(cRefStickerSetsOrder());
|
||||
auto &order(Global::RefStickerSetsOrder());
|
||||
int32 insertAtIndex = 0, currentIndex = order.indexOf(s.vid.v);
|
||||
if (currentIndex != insertAtIndex) {
|
||||
if (currentIndex > 0) {
|
||||
|
@ -4727,7 +4725,7 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
order.insert(insertAtIndex, s.vid.v);
|
||||
}
|
||||
|
||||
StickerSets::iterator custom = sets.find(CustomStickerSetId);
|
||||
auto custom = sets.find(Stickers::CustomSetId);
|
||||
if (custom != sets.cend()) {
|
||||
for (int32 i = 0, l = it->stickers.size(); i < l; ++i) {
|
||||
int32 removeIndex = custom->stickers.indexOf(it->stickers.at(i));
|
||||
|
@ -4746,26 +4744,26 @@ void MainWidget::feedUpdate(const MTPUpdate &update) {
|
|||
case mtpc_updateStickerSetsOrder: {
|
||||
const MTPDupdateStickerSetsOrder &d(update.c_updateStickerSetsOrder());
|
||||
const QVector<MTPlong> &order(d.vorder.c_vector().v);
|
||||
const StickerSets &sets(cStickerSets());
|
||||
StickerSetsOrder result;
|
||||
const Stickers::Sets &sets(Global::StickerSets());
|
||||
Stickers::Order result;
|
||||
for (int32 i = 0, l = order.size(); i < l; ++i) {
|
||||
if (sets.constFind(order.at(i).v) == sets.cend()) {
|
||||
break;
|
||||
}
|
||||
result.push_back(order.at(i).v);
|
||||
}
|
||||
if (result.size() != cStickerSetsOrder().size() || result.size() != order.size()) {
|
||||
cSetLastStickersUpdate(0);
|
||||
if (result.size() != Global::StickerSetsOrder().size() || result.size() != order.size()) {
|
||||
Global::SetLastStickersUpdate(0);
|
||||
App::main()->updateStickers();
|
||||
} else {
|
||||
cSetStickerSetsOrder(result);
|
||||
Global::SetStickerSetsOrder(result);
|
||||
Local::writeStickers();
|
||||
emit stickersUpdated();
|
||||
}
|
||||
} break;
|
||||
|
||||
case mtpc_updateStickerSets: {
|
||||
cSetLastStickersUpdate(0);
|
||||
Global::SetLastStickersUpdate(0);
|
||||
App::main()->updateStickers();
|
||||
} break;
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ public slots:
|
|||
void onDeleteContactSure();
|
||||
void onDeleteAndExit();
|
||||
void onDeleteAndExitSure();
|
||||
void onSearch();
|
||||
|
||||
signals:
|
||||
|
||||
|
@ -105,6 +106,8 @@ private:
|
|||
FlatButton _edit, _leaveGroup, _addContact, _deleteContact;
|
||||
FlatButton _mediaType;
|
||||
|
||||
IconedButton _search;
|
||||
|
||||
PlainShadow _sideShadow;
|
||||
|
||||
};
|
||||
|
@ -248,6 +251,7 @@ public:
|
|||
void sentUpdatesReceived(const MTPUpdates &updates) {
|
||||
return sentUpdatesReceived(0, updates);
|
||||
}
|
||||
bool deleteChannelFailed(const RPCError &error);
|
||||
void inviteToChannelDone(ChannelData *channel, const MTPUpdates &updates);
|
||||
void historyToDown(History *hist);
|
||||
void dialogsToUp();
|
||||
|
@ -441,9 +445,9 @@ public:
|
|||
void notify_userIsContactChanged(UserData *user, bool fromThisApp);
|
||||
void notify_migrateUpdated(PeerData *peer);
|
||||
void notify_clipStopperHidden(ClipStopperType type);
|
||||
void notify_historyItemResized(const HistoryItem *row, bool scrollToIt);
|
||||
void notify_historyItemLayoutChanged(const HistoryItem *item);
|
||||
void notify_automaticLoadSettingsChangedGif();
|
||||
void notify_handlePendingHistoryUpdate();
|
||||
|
||||
void cmd_search();
|
||||
void cmd_next_chat();
|
||||
|
@ -517,6 +521,7 @@ public slots:
|
|||
void onDownloadPathSettings();
|
||||
|
||||
void ui_showPeerHistoryAsync(quint64 peerId, qint32 showAtMsgId);
|
||||
void ui_autoplayMediaInlineAsync(qint32 channelId, qint32 msgId);
|
||||
|
||||
private:
|
||||
|
||||
|
|
|
@ -313,7 +313,7 @@ void MediaView::updateControls() {
|
|||
_docRadial.start(_doc->progress());
|
||||
}
|
||||
} else {
|
||||
if (_doc->loaded(true)) {
|
||||
if (_doc->loaded(DocumentData::FilePathResolveChecked)) {
|
||||
_docDownload.hide();
|
||||
_docSaveAs.moveToLeft(_docRect.x() + 2 * st::mvDocPadding + st::mvDocIconSize, _docRect.y() + st::mvDocPadding + st::mvDocLinksTop);
|
||||
_docSaveAs.show();
|
||||
|
@ -333,7 +333,7 @@ void MediaView::updateControls() {
|
|||
_docCancel.hide();
|
||||
}
|
||||
|
||||
_saveVisible = ((_photo && _photo->loaded()) || (_doc && (_doc->loaded(true) || (!fileShown() && (_photo || _doc)))));
|
||||
_saveVisible = ((_photo && _photo->loaded()) || (_doc && (_doc->loaded(DocumentData::FilePathResolveChecked) || (!fileShown() && (_photo || _doc)))));
|
||||
_saveNav = myrtlrect(width() - st::mvIconSize.width() * 2, height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height());
|
||||
_saveNavIcon = centersprite(_saveNav, st::mvSave);
|
||||
_moreNav = myrtlrect(width() - st::mvIconSize.width(), height() - st::mvIconSize.height(), st::mvIconSize.width(), st::mvIconSize.height());
|
||||
|
@ -394,7 +394,7 @@ void MediaView::updateControls() {
|
|||
void MediaView::updateDropdown() {
|
||||
_btnSaveCancel->setVisible(_doc && _doc->loading());
|
||||
_btnToMessage->setVisible(_msgid > 0);
|
||||
_btnShowInFolder->setVisible(_doc && !_doc->already(true).isEmpty());
|
||||
_btnShowInFolder->setVisible(_doc && !_doc->filepath(DocumentData::FilePathResolveChecked).isEmpty());
|
||||
_btnSaveAs->setVisible(true);
|
||||
_btnCopy->setVisible((_doc && fileShown()) || (_photo && _photo->loaded()));
|
||||
_btnForward->setVisible(_canForward);
|
||||
|
@ -685,8 +685,11 @@ void MediaView::onSaveCancel() {
|
|||
|
||||
void MediaView::onShowInFolder() {
|
||||
if (!_doc) return;
|
||||
QString already(_doc->already(true));
|
||||
if (!already.isEmpty()) psShowInFolder(already);
|
||||
|
||||
QString filepath = _doc->filepath(DocumentData::FilePathResolveChecked);
|
||||
if (!filepath.isEmpty()) {
|
||||
psShowInFolder(filepath);
|
||||
}
|
||||
}
|
||||
|
||||
void MediaView::onForward() {
|
||||
|
@ -964,7 +967,7 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty
|
|||
if (!_doc->data().isEmpty() && _doc->isAnimation()) {
|
||||
if (!_gif) {
|
||||
if (_doc->dimensions.width() && _doc->dimensions.height()) {
|
||||
_current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), true, true, false, _doc->dimensions.width(), _doc->dimensions.height());
|
||||
_current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height());
|
||||
}
|
||||
_gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback));
|
||||
}
|
||||
|
@ -972,7 +975,7 @@ void MediaView::displayDocument(DocumentData *doc, HistoryItem *item) { // empty
|
|||
if (_doc->isAnimation()) {
|
||||
if (!_gif) {
|
||||
if (_doc->dimensions.width() && _doc->dimensions.height()) {
|
||||
_current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), true, true, false, _doc->dimensions.width(), _doc->dimensions.height());
|
||||
_current = _doc->thumb->pixNoCache(_doc->dimensions.width(), _doc->dimensions.height(), ImagePixSmooth | ImagePixBlurred, _doc->dimensions.width(), _doc->dimensions.height());
|
||||
}
|
||||
_gif = new ClipReader(location, _doc->data(), func(this, &MediaView::clipCallback));
|
||||
}
|
||||
|
@ -1113,17 +1116,17 @@ void MediaView::paintEvent(QPaintEvent *e) {
|
|||
int32 w = _width * cIntRetinaFactor();
|
||||
if (_full <= 0 && _photo->loaded()) {
|
||||
int32 h = int((_photo->full->height() * (qreal(w) / qreal(_photo->full->width()))) + 0.9999);
|
||||
_current = _photo->full->pixNoCache(w, h, true);
|
||||
_current = _photo->full->pixNoCache(w, h, ImagePixSmooth);
|
||||
if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
|
||||
_full = 1;
|
||||
} else if (_full < 0 && _photo->medium->loaded()) {
|
||||
int32 h = int((_photo->full->height() * (qreal(w) / qreal(_photo->full->width()))) + 0.9999);
|
||||
_current = _photo->medium->pixNoCache(w, h, true, true);
|
||||
_current = _photo->medium->pixNoCache(w, h, ImagePixSmooth | ImagePixBlurred);
|
||||
if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
|
||||
_full = 0;
|
||||
} else if (_current.isNull() && _photo->thumb->loaded()) {
|
||||
int32 h = int((_photo->full->height() * (qreal(w) / qreal(_photo->full->width()))) + 0.9999);
|
||||
_current = _photo->thumb->pixNoCache(w, h, true, true);
|
||||
_current = _photo->thumb->pixNoCache(w, h, ImagePixSmooth | ImagePixBlurred);
|
||||
if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
|
||||
} else if (_current.isNull()) {
|
||||
_current = _photo->thumb->pix();
|
||||
|
|
59
Telegram/SourceFiles/mtproto/auth_key.cpp
Normal file
59
Telegram/SourceFiles/mtproto/auth_key.cpp
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "mtproto/auth_key.h"
|
||||
|
||||
#include <openssl/aes.h>
|
||||
|
||||
namespace MTP {
|
||||
|
||||
void aesIgeEncrypt(const void *src, void *dst, uint32 len, const void *key, const void *iv) {
|
||||
uchar aes_key[32], aes_iv[32];
|
||||
memcpy(aes_key, key, 32);
|
||||
memcpy(aes_iv, iv, 32);
|
||||
|
||||
AES_KEY aes;
|
||||
AES_set_encrypt_key(aes_key, 256, &aes);
|
||||
AES_ige_encrypt(static_cast<const uchar*>(src), static_cast<uchar*>(dst), len, &aes, aes_iv, AES_ENCRYPT);
|
||||
}
|
||||
|
||||
void aesIgeDecrypt(const void *src, void *dst, uint32 len, const void *key, const void *iv) {
|
||||
uchar aes_key[32], aes_iv[32];
|
||||
memcpy(aes_key, key, 32);
|
||||
memcpy(aes_iv, iv, 32);
|
||||
|
||||
AES_KEY aes;
|
||||
AES_set_decrypt_key(aes_key, 256, &aes);
|
||||
AES_ige_encrypt(static_cast<const uchar*>(src), static_cast<uchar*>(dst), len, &aes, aes_iv, AES_DECRYPT);
|
||||
}
|
||||
|
||||
void aesCtrEncrypt(void *data, uint32 len, const void *key, CTRState *state) {
|
||||
AES_KEY aes;
|
||||
AES_set_encrypt_key(static_cast<const uchar*>(key), 256, &aes);
|
||||
|
||||
static_assert(CTRState::IvecSize == AES_BLOCK_SIZE, "Wrong size of ctr ivec!");
|
||||
static_assert(CTRState::EcountSize == AES_BLOCK_SIZE, "Wrong size of ctr ecount!");
|
||||
|
||||
AES_ctr128_encrypt(static_cast<const uchar*>(data), static_cast<uchar*>(data), len, &aes, state->ivec, state->ecount, &state->num);
|
||||
}
|
||||
|
||||
} // namespace MTP
|
|
@ -20,10 +20,12 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
class mtpAuthKey {
|
||||
namespace MTP {
|
||||
|
||||
class AuthKey {
|
||||
public:
|
||||
|
||||
mtpAuthKey() : _isset(false), _dc(0) {
|
||||
AuthKey() : _isset(false), _dc(0) {
|
||||
}
|
||||
|
||||
bool created() const {
|
||||
|
@ -52,7 +54,7 @@ public:
|
|||
}
|
||||
|
||||
void prepareAES(const MTPint128 &msgKey, MTPint256 &aesKey, MTPint256 &aesIV, bool send = true) const {
|
||||
if (!_isset) throw mtpErrorKeyNotReady(QString("prepareAES(.., %1)").arg(Logs::b(send)));
|
||||
if (!_isset) throw mtpErrorKeyNotReady(QString("prepareAES(..., %1)").arg(Logs::b(send)));
|
||||
|
||||
uint32 x = send ? 0 : 8;
|
||||
|
||||
|
@ -88,13 +90,13 @@ public:
|
|||
}
|
||||
|
||||
void write(QDataStream &to) const {
|
||||
if (!_isset) throw mtpErrorKeyNotReady("write(..)");
|
||||
if (!_isset) throw mtpErrorKeyNotReady("write(...)");
|
||||
to.writeRawData(_key, 256);
|
||||
}
|
||||
|
||||
static const uint64 RecreateKeyId = 0xFFFFFFFFFFFFFFFFL;
|
||||
|
||||
friend bool operator==(const mtpAuthKey &a, const mtpAuthKey &b);
|
||||
friend bool operator==(const AuthKey &a, const AuthKey &b);
|
||||
|
||||
private:
|
||||
|
||||
|
@ -105,40 +107,54 @@ private:
|
|||
|
||||
};
|
||||
|
||||
inline bool operator==(const mtpAuthKey &a, const mtpAuthKey &b) {
|
||||
inline bool operator==(const AuthKey &a, const AuthKey &b) {
|
||||
return !memcmp(a._key, b._key, 256);
|
||||
}
|
||||
|
||||
typedef QSharedPointer<mtpAuthKey> mtpAuthKeyPtr;
|
||||
typedef QVector<mtpAuthKeyPtr> mtpKeysMap;
|
||||
typedef QSharedPointer<AuthKey> AuthKeyPtr;
|
||||
typedef QVector<AuthKeyPtr> AuthKeysMap;
|
||||
|
||||
void aesEncrypt(const void *src, void *dst, uint32 len, void *key, void *iv);
|
||||
void aesDecrypt(const void *src, void *dst, uint32 len, void *key, void *iv);
|
||||
void aesIgeEncrypt(const void *src, void *dst, uint32 len, const void *key, const void *iv);
|
||||
void aesIgeDecrypt(const void *src, void *dst, uint32 len, const void *key, const void *iv);
|
||||
|
||||
inline void aesEncrypt(const void *src, void *dst, uint32 len, const mtpAuthKeyPtr &authKey, const MTPint128 &msgKey) {
|
||||
inline void aesIgeEncrypt(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES(msgKey, aesKey, aesIV);
|
||||
|
||||
return aesEncrypt(src, dst, len, &aesKey, &aesIV);
|
||||
return aesIgeEncrypt(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
inline void aesEncryptLocal(const void *src, void *dst, uint32 len, const mtpAuthKey *authKey, const void *key128) {
|
||||
inline void aesEncryptLocal(const void *src, void *dst, uint32 len, const AuthKey *authKey, const void *key128) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES(*(const MTPint128*)key128, aesKey, aesIV, false);
|
||||
|
||||
return aesEncrypt(src, dst, len, &aesKey, &aesIV);
|
||||
return aesIgeEncrypt(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
inline void aesDecrypt(const void *src, void *dst, uint32 len, const mtpAuthKeyPtr &authKey, const MTPint128 &msgKey) {
|
||||
inline void aesIgeDecrypt(const void *src, void *dst, uint32 len, const AuthKeyPtr &authKey, const MTPint128 &msgKey) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES(msgKey, aesKey, aesIV, false);
|
||||
|
||||
return aesDecrypt(src, dst, len, &aesKey, &aesIV);
|
||||
return aesIgeDecrypt(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
inline void aesDecryptLocal(const void *src, void *dst, uint32 len, const mtpAuthKey *authKey, const void *key128) {
|
||||
inline void aesDecryptLocal(const void *src, void *dst, uint32 len, const AuthKey *authKey, const void *key128) {
|
||||
MTPint256 aesKey, aesIV;
|
||||
authKey->prepareAES(*(const MTPint128*)key128, aesKey, aesIV, false);
|
||||
|
||||
return aesDecrypt(src, dst, len, &aesKey, &aesIV);
|
||||
return aesIgeDecrypt(src, dst, len, static_cast<const void*>(&aesKey), static_cast<const void*>(&aesIV));
|
||||
}
|
||||
|
||||
// ctr used inplace, encrypt the data and leave it at the same place
|
||||
struct CTRState {
|
||||
static constexpr int KeySize = 32;
|
||||
static constexpr int IvecSize = 16;
|
||||
static constexpr int EcountSize = 16;
|
||||
|
||||
uchar ivec[IvecSize] = { 0 };
|
||||
uint32 num = 0;
|
||||
uchar ecount[EcountSize] = { 0 };
|
||||
};
|
||||
void aesCtrEncrypt(void *data, uint32 len, const void *key, CTRState *state);
|
||||
|
||||
} // namespace MTP
|
File diff suppressed because it is too large
Load diff
268
Telegram/SourceFiles/mtproto/connection.h
Normal file
268
Telegram/SourceFiles/mtproto/connection.h
Normal file
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/auth_key.h"
|
||||
#include "mtproto/connection_abstract.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
class ConnectionPrivate;
|
||||
class SessionData;
|
||||
|
||||
class Thread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Thread();
|
||||
uint32 getThreadId() const;
|
||||
~Thread();
|
||||
|
||||
private:
|
||||
uint32 _threadId;
|
||||
|
||||
};
|
||||
|
||||
class Connection {
|
||||
public:
|
||||
|
||||
enum ConnectionType {
|
||||
TcpConnection,
|
||||
HttpConnection
|
||||
};
|
||||
|
||||
Connection();
|
||||
int32 start(SessionData *data, int32 dc = 0); // return dc
|
||||
void kill();
|
||||
void waitTillFinish();
|
||||
~Connection();
|
||||
|
||||
static const int UpdateAlways = 666;
|
||||
|
||||
int32 state() const;
|
||||
QString transport() const;
|
||||
|
||||
private:
|
||||
|
||||
QThread *thread;
|
||||
ConnectionPrivate *data;
|
||||
|
||||
};
|
||||
|
||||
class ConnectionPrivate : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
ConnectionPrivate(QThread *thread, Connection *owner, SessionData *data, uint32 dc);
|
||||
~ConnectionPrivate();
|
||||
|
||||
void stop();
|
||||
|
||||
int32 getDC() const;
|
||||
|
||||
int32 getState() const;
|
||||
QString transport() const;
|
||||
|
||||
signals:
|
||||
|
||||
void needToReceive();
|
||||
void needToRestart();
|
||||
void stateChanged(qint32 newState);
|
||||
void sessionResetDone();
|
||||
|
||||
void needToSendAsync();
|
||||
void sendAnythingAsync(quint64 msWait);
|
||||
void sendHttpWaitAsync();
|
||||
void sendPongAsync(quint64 msgId, quint64 pingId);
|
||||
void sendMsgsStateInfoAsync(quint64 msgId, QByteArray data);
|
||||
void resendAsync(quint64 msgId, quint64 msCanWait, bool forceContainer, bool sendMsgStateInfo);
|
||||
void resendManyAsync(QVector<quint64> msgIds, quint64 msCanWait, bool forceContainer, bool sendMsgStateInfo);
|
||||
void resendAllAsync();
|
||||
|
||||
void finished(Connection *connection);
|
||||
|
||||
public slots:
|
||||
|
||||
void retryByTimer();
|
||||
void restartNow();
|
||||
void restart(bool mayBeBadKey = false);
|
||||
|
||||
void onPingSender();
|
||||
void onPingSendForce();
|
||||
|
||||
void onWaitConnectedFailed();
|
||||
void onWaitReceivedFailed();
|
||||
void onWaitIPv4Failed();
|
||||
|
||||
void onOldConnection();
|
||||
void onSentSome(uint64 size);
|
||||
void onReceivedSome();
|
||||
|
||||
void onReadyData();
|
||||
void socketStart(bool afterConfig = false);
|
||||
|
||||
void onConnected4();
|
||||
void onConnected6();
|
||||
void onDisconnected4();
|
||||
void onDisconnected6();
|
||||
void onError4(bool mayBeBadKey = false);
|
||||
void onError6(bool mayBeBadKey = false);
|
||||
|
||||
void doFinish();
|
||||
|
||||
// Auth key creation packet receive slots
|
||||
void pqAnswered();
|
||||
void dhParamsAnswered();
|
||||
void dhClientParamsAnswered();
|
||||
|
||||
// General packet receive slot, connected to conn->receivedData signal
|
||||
void handleReceived();
|
||||
|
||||
// Sessions signals, when we need to send something
|
||||
void tryToSend();
|
||||
|
||||
void updateAuthKey();
|
||||
|
||||
void onConfigLoaded();
|
||||
|
||||
private:
|
||||
|
||||
void doDisconnect();
|
||||
|
||||
void createConn(bool createIPv4, bool createIPv6);
|
||||
void destroyConn(AbstractConnection **conn = 0); // 0 - destory all
|
||||
|
||||
mtpMsgId placeToContainer(mtpRequest &toSendRequest, mtpMsgId &bigMsgId, mtpMsgId *&haveSentArr, mtpRequest &req);
|
||||
mtpMsgId prepareToSend(mtpRequest &request, mtpMsgId currentLastId);
|
||||
mtpMsgId replaceMsgId(mtpRequest &request, mtpMsgId newId);
|
||||
|
||||
bool sendRequest(mtpRequest &request, bool needAnyResponse, QReadLocker &lockFinished);
|
||||
mtpRequestId wasSent(mtpMsgId msgId) const;
|
||||
|
||||
int32 handleOneReceived(const mtpPrime *from, const mtpPrime *end, uint64 msgId, int32 serverTime, uint64 serverSalt, bool badTime);
|
||||
mtpBuffer ungzip(const mtpPrime *from, const mtpPrime *end) const;
|
||||
void handleMsgsStates(const QVector<MTPlong> &ids, const string &states, QVector<MTPlong> &acked);
|
||||
|
||||
void clearMessages();
|
||||
|
||||
bool setState(int32 state, int32 ifState = Connection::UpdateAlways);
|
||||
mutable QReadWriteLock stateConnMutex;
|
||||
int32 _state;
|
||||
|
||||
bool _needSessionReset;
|
||||
void resetSession();
|
||||
|
||||
ShiftedDcId dc;
|
||||
Connection *_owner;
|
||||
AbstractConnection *_conn, *_conn4, *_conn6;
|
||||
|
||||
SingleTimer retryTimer; // exp retry timer
|
||||
int retryTimeout;
|
||||
quint64 retryWillFinish;
|
||||
|
||||
SingleTimer oldConnectionTimer;
|
||||
bool oldConnection;
|
||||
|
||||
SingleTimer _waitForConnectedTimer, _waitForReceivedTimer, _waitForIPv4Timer;
|
||||
uint32 _waitForReceived, _waitForConnected;
|
||||
int64 firstSentAt;
|
||||
|
||||
QVector<MTPlong> ackRequestData, resendRequestData;
|
||||
|
||||
// if badTime received - search for ids in sessionData->haveSent and sessionData->wereAcked and sync time/salt, return true if found
|
||||
bool requestsFixTimeSalt(const QVector<MTPlong> &ids, int32 serverTime, uint64 serverSalt);
|
||||
|
||||
// remove msgs with such ids from sessionData->haveSent, add to sessionData->wereAcked
|
||||
void requestsAcked(const QVector<MTPlong> &ids, bool byResponse = false);
|
||||
|
||||
mtpPingId _pingId, _pingIdToSend;
|
||||
uint64 _pingSendAt;
|
||||
mtpMsgId _pingMsgId;
|
||||
SingleTimer _pingSender;
|
||||
|
||||
void resend(quint64 msgId, quint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false);
|
||||
void resendMany(QVector<quint64> msgIds, quint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false);
|
||||
|
||||
template <typename TRequest>
|
||||
void sendRequestNotSecure(const TRequest &request);
|
||||
|
||||
template <typename TResponse>
|
||||
bool readResponseNotSecure(TResponse &response);
|
||||
|
||||
bool restarted, _finished;
|
||||
|
||||
uint64 keyId;
|
||||
QReadWriteLock sessionDataMutex;
|
||||
SessionData *sessionData;
|
||||
|
||||
bool myKeyLock;
|
||||
void lockKey();
|
||||
void unlockKey();
|
||||
|
||||
// Auth key creation fields and methods
|
||||
struct AuthKeyCreateData {
|
||||
AuthKeyCreateData()
|
||||
: new_nonce(*(MTPint256*)((uchar*)new_nonce_buf))
|
||||
, auth_key_aux_hash(*(MTPlong*)((uchar*)new_nonce_buf + 33))
|
||||
, retries(0)
|
||||
, g(0)
|
||||
, req_num(0)
|
||||
, msgs_sent(0) {
|
||||
memset(new_nonce_buf, 0, sizeof(new_nonce_buf));
|
||||
memset(aesKey, 0, sizeof(aesKey));
|
||||
memset(aesIV, 0, sizeof(aesIV));
|
||||
memset(auth_key, 0, sizeof(auth_key));
|
||||
}
|
||||
MTPint128 nonce, server_nonce;
|
||||
uchar new_nonce_buf[41]; // 32 bytes new_nonce + 1 check byte + 8 bytes of auth_key_aux_hash
|
||||
MTPint256 &new_nonce;
|
||||
MTPlong &auth_key_aux_hash;
|
||||
|
||||
uint32 retries;
|
||||
MTPlong retry_id;
|
||||
|
||||
int32 g;
|
||||
|
||||
uchar aesKey[32], aesIV[32];
|
||||
uint32 auth_key[64];
|
||||
MTPlong auth_key_hash;
|
||||
|
||||
uint32 req_num; // sent not encrypted request number
|
||||
uint32 msgs_sent;
|
||||
};
|
||||
struct AuthKeyCreateStrings {
|
||||
QByteArray dh_prime;
|
||||
QByteArray g_a;
|
||||
};
|
||||
AuthKeyCreateData *authKeyData;
|
||||
AuthKeyCreateStrings *authKeyStrings;
|
||||
|
||||
void dhClientParamsSend();
|
||||
void authKeyCreated();
|
||||
void clearAuthKeyData();
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
90
Telegram/SourceFiles/mtproto/connection_abstract.cpp
Normal file
90
Telegram/SourceFiles/mtproto/connection_abstract.cpp
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "mtproto/connection_abstract.h"
|
||||
|
||||
#include "mtproto/connection_tcp.h"
|
||||
#include "mtproto/connection_http.h"
|
||||
#include "mtproto/connection_auto.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
AbstractConnection::~AbstractConnection() {
|
||||
}
|
||||
|
||||
mtpBuffer AbstractConnection::preparePQFake(const MTPint128 &nonce) {
|
||||
MTPReq_pq req_pq(nonce);
|
||||
mtpBuffer buffer;
|
||||
uint32 requestSize = req_pq.innerLength() >> 2;
|
||||
|
||||
buffer.resize(0);
|
||||
buffer.reserve(8 + requestSize);
|
||||
buffer.push_back(0); // tcp packet len
|
||||
buffer.push_back(0); // tcp packet num
|
||||
buffer.push_back(0);
|
||||
buffer.push_back(0);
|
||||
buffer.push_back(0);
|
||||
buffer.push_back(unixtime());
|
||||
buffer.push_back(requestSize * 4);
|
||||
req_pq.write(buffer);
|
||||
buffer.push_back(0); // tcp crc32 hash
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
MTPResPQ AbstractConnection::readPQFakeReply(const mtpBuffer &buffer) {
|
||||
const mtpPrime *answer(buffer.constData());
|
||||
uint32 len = buffer.size();
|
||||
if (len < 5) {
|
||||
LOG(("Fake PQ Error: bad request answer, len = %1").arg(len * sizeof(mtpPrime)));
|
||||
DEBUG_LOG(("Fake PQ Error: answer bytes %1").arg(Logs::mb(answer, len * sizeof(mtpPrime)).str()));
|
||||
throw Exception("bad pq reply");
|
||||
}
|
||||
if (answer[0] != 0 || answer[1] != 0 || (((uint32)answer[2]) & 0x03) != 1/* || (unixtime() - answer[3] > 300) || (answer[3] - unixtime() > 60)*/) { // didnt sync time yet
|
||||
LOG(("Fake PQ Error: bad request answer start (%1 %2 %3)").arg(answer[0]).arg(answer[1]).arg(answer[2]));
|
||||
DEBUG_LOG(("Fake PQ Error: answer bytes %1").arg(Logs::mb(answer, len * sizeof(mtpPrime)).str()));
|
||||
throw Exception("bad pq reply");
|
||||
}
|
||||
uint32 answerLen = (uint32)answer[4];
|
||||
if (answerLen != (len - 5) * sizeof(mtpPrime)) {
|
||||
LOG(("Fake PQ Error: bad request answer %1 <> %2").arg(answerLen).arg((len - 5) * sizeof(mtpPrime)));
|
||||
DEBUG_LOG(("Fake PQ Error: answer bytes %1").arg(Logs::mb(answer, len * sizeof(mtpPrime)).str()));
|
||||
throw Exception("bad pq reply");
|
||||
}
|
||||
const mtpPrime *from(answer + 5), *end(from + len - 5);
|
||||
MTPResPQ response;
|
||||
response.read(from, end);
|
||||
return response;
|
||||
}
|
||||
|
||||
AbstractConnection *AbstractConnection::create(QThread *thread) {
|
||||
if (cConnectionType() == dbictHttpProxy) {
|
||||
return new HTTPConnection(thread);
|
||||
} else if (cConnectionType() == dbictTcpProxy) {
|
||||
return new TCPConnection(thread);
|
||||
}
|
||||
return new AutoConnection(thread);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
91
Telegram/SourceFiles/mtproto/connection_abstract.h
Normal file
91
Telegram/SourceFiles/mtproto/connection_abstract.h
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
class AbstractConnection : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AbstractConnection(QThread *thread) : _sentEncrypted(false) {
|
||||
moveToThread(thread);
|
||||
}
|
||||
AbstractConnection(const AbstractConnection &other) = delete;
|
||||
AbstractConnection &operator=(const AbstractConnection &other) = delete;
|
||||
virtual ~AbstractConnection() = 0;
|
||||
|
||||
// virtual constructor
|
||||
static AbstractConnection *create(QThread *thread);
|
||||
|
||||
void setSentEncrypted() {
|
||||
_sentEncrypted = true;
|
||||
}
|
||||
|
||||
virtual void sendData(mtpBuffer &buffer) = 0; // has size + 3, buffer[0] = len, buffer[1] = packetnum, buffer[last] = crc32
|
||||
virtual void disconnectFromServer() = 0;
|
||||
virtual void connectTcp(const QString &addr, int32 port, MTPDdcOption::Flags flags) = 0;
|
||||
virtual void connectHttp(const QString &addr, int32 port, MTPDdcOption::Flags flags) = 0;
|
||||
virtual bool isConnected() const = 0;
|
||||
virtual bool usingHttpWait() {
|
||||
return false;
|
||||
}
|
||||
virtual bool needHttpWait() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual int32 debugState() const = 0;
|
||||
|
||||
virtual QString transport() const = 0;
|
||||
|
||||
typedef QList<mtpBuffer> BuffersQueue;
|
||||
BuffersQueue &received() {
|
||||
return receivedQueue;
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
void receivedData();
|
||||
void receivedSome(); // to stop restart timer
|
||||
|
||||
void error(bool mayBeBadKey = false);
|
||||
|
||||
void connected();
|
||||
void disconnected();
|
||||
|
||||
protected:
|
||||
|
||||
BuffersQueue receivedQueue; // list of received packets, not processed yet
|
||||
bool _sentEncrypted;
|
||||
|
||||
// first we always send fake MTPReq_pq to see if connection works at all
|
||||
// we send them simultaneously through TCP/HTTP/IPv4/IPv6 to choose the working one
|
||||
static mtpBuffer preparePQFake(const MTPint128 &nonce);
|
||||
static MTPResPQ readPQFakeReply(const mtpBuffer &buffer);
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
344
Telegram/SourceFiles/mtproto/connection_auto.cpp
Normal file
344
Telegram/SourceFiles/mtproto/connection_auto.cpp
Normal file
|
@ -0,0 +1,344 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "mtproto/connection_auto.h"
|
||||
|
||||
#include "mtproto/connection_http.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
AutoConnection::AutoConnection(QThread *thread) : AbstractTCPConnection(thread)
|
||||
, status(WaitingBoth)
|
||||
, tcpNonce(rand_value<MTPint128>())
|
||||
, httpNonce(rand_value<MTPint128>())
|
||||
, _flagsTcp(0)
|
||||
, _flagsHttp(0)
|
||||
, _tcpTimeout(MTPMinReceiveDelay) {
|
||||
manager.moveToThread(thread);
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
manager.setProxy(QNetworkProxy(QNetworkProxy::DefaultProxy));
|
||||
#endif
|
||||
|
||||
httpStartTimer.moveToThread(thread);
|
||||
httpStartTimer.setSingleShot(true);
|
||||
connect(&httpStartTimer, SIGNAL(timeout()), this, SLOT(onHttpStart()));
|
||||
|
||||
tcpTimeoutTimer.moveToThread(thread);
|
||||
tcpTimeoutTimer.setSingleShot(true);
|
||||
connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer()));
|
||||
|
||||
sock.moveToThread(thread);
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
sock.setProxy(QNetworkProxy(QNetworkProxy::NoProxy));
|
||||
#endif
|
||||
connect(&sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
|
||||
connect(&sock, SIGNAL(connected()), this, SLOT(onSocketConnected()));
|
||||
connect(&sock, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
|
||||
}
|
||||
|
||||
void AutoConnection::onHttpStart() {
|
||||
if (status == HttpReady) {
|
||||
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by timer").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
status = UsingHttp;
|
||||
sock.disconnectFromHost();
|
||||
emit connected();
|
||||
}
|
||||
}
|
||||
|
||||
void AutoConnection::onSocketConnected() {
|
||||
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
|
||||
mtpBuffer buffer(preparePQFake(tcpNonce));
|
||||
|
||||
DEBUG_LOG(("Connection Info: sending fake req_pq through TCP/%1 transport").arg((_flagsTcp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
|
||||
if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout;
|
||||
tcpTimeoutTimer.start(_tcpTimeout);
|
||||
|
||||
tcpSend(buffer);
|
||||
} else if (status == WaitingHttp || status == UsingHttp) {
|
||||
sock.disconnectFromHost();
|
||||
}
|
||||
}
|
||||
|
||||
void AutoConnection::onTcpTimeoutTimer() {
|
||||
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
|
||||
if (_tcpTimeout < MTPMaxReceiveDelay) _tcpTimeout *= 2;
|
||||
_tcpTimeout = -_tcpTimeout;
|
||||
|
||||
QAbstractSocket::SocketState state = sock.state();
|
||||
if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) {
|
||||
sock.disconnectFromHost();
|
||||
} else if (state != QAbstractSocket::ClosingState) {
|
||||
sock.connectToHost(QHostAddress(_addrTcp), _portTcp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AutoConnection::onSocketDisconnected() {
|
||||
if (_tcpTimeout < 0) {
|
||||
_tcpTimeout = -_tcpTimeout;
|
||||
if (status == HttpReady || status == WaitingBoth || status == WaitingTcp) {
|
||||
sock.connectToHost(QHostAddress(_addrTcp), _portTcp);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (status == WaitingBoth) {
|
||||
status = WaitingHttp;
|
||||
} else if (status == WaitingTcp || status == UsingTcp) {
|
||||
emit disconnected();
|
||||
} else if (status == HttpReady) {
|
||||
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by socket disconnect").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
status = UsingHttp;
|
||||
emit connected();
|
||||
}
|
||||
}
|
||||
|
||||
void AutoConnection::sendData(mtpBuffer &buffer) {
|
||||
if (status == FinishedWork) return;
|
||||
|
||||
if (buffer.size() < 3) {
|
||||
LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime)));
|
||||
TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str()));
|
||||
emit error();
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == UsingTcp) {
|
||||
tcpSend(buffer);
|
||||
} else {
|
||||
httpSend(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void AutoConnection::httpSend(mtpBuffer &buffer) {
|
||||
int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime);
|
||||
|
||||
QNetworkRequest request(address);
|
||||
request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded")));
|
||||
|
||||
TCP_LOG(("HTTP Info: sending %1 len request").arg(requestSize));
|
||||
requests.insert(manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize)));
|
||||
}
|
||||
|
||||
void AutoConnection::disconnectFromServer() {
|
||||
if (status == FinishedWork) return;
|
||||
status = FinishedWork;
|
||||
|
||||
Requests copy = requests;
|
||||
requests.clear();
|
||||
for (Requests::const_iterator i = copy.cbegin(), e = copy.cend(); i != e; ++i) {
|
||||
(*i)->abort();
|
||||
(*i)->deleteLater();
|
||||
}
|
||||
|
||||
disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
|
||||
|
||||
address = QUrl();
|
||||
|
||||
disconnect(&sock, SIGNAL(readyRead()), 0, 0);
|
||||
sock.close();
|
||||
|
||||
httpStartTimer.stop();
|
||||
}
|
||||
|
||||
void AutoConnection::connectTcp(const QString &addr, int32 port, MTPDdcOption::Flags flags) {
|
||||
_addrTcp = addr;
|
||||
_portTcp = port;
|
||||
_flagsTcp = flags;
|
||||
|
||||
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
|
||||
sock.connectToHost(QHostAddress(_addrTcp), _portTcp);
|
||||
}
|
||||
|
||||
void AutoConnection::connectHttp(const QString &addr, int32 port, MTPDdcOption::Flags flags) {
|
||||
address = QUrl(((flags & MTPDdcOption::Flag::f_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80));//not p - always 80 port for http transport
|
||||
TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString()));
|
||||
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
|
||||
|
||||
_addrHttp = addr;
|
||||
_portHttp = port;
|
||||
_flagsHttp = flags;
|
||||
|
||||
mtpBuffer buffer(preparePQFake(httpNonce));
|
||||
|
||||
DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP/%1 transport").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
|
||||
httpSend(buffer);
|
||||
}
|
||||
|
||||
bool AutoConnection::isConnected() const {
|
||||
return (status == UsingTcp) || (status == UsingHttp);
|
||||
}
|
||||
|
||||
void AutoConnection::requestFinished(QNetworkReply *reply) {
|
||||
if (status == FinishedWork) return;
|
||||
|
||||
reply->deleteLater();
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
requests.remove(reply);
|
||||
|
||||
mtpBuffer data = HTTPConnection::handleResponse(reply);
|
||||
if (data.size() == 1) {
|
||||
if (status == WaitingBoth) {
|
||||
status = WaitingTcp;
|
||||
} else {
|
||||
emit error();
|
||||
}
|
||||
} else if (!data.isEmpty()) {
|
||||
if (status == UsingHttp) {
|
||||
receivedQueue.push_back(data);
|
||||
emit receivedData();
|
||||
} else if (status == WaitingBoth || status == WaitingHttp) {
|
||||
try {
|
||||
MTPResPQ res_pq = readPQFakeReply(data);
|
||||
const MTPDresPQ &res_pq_data(res_pq.c_resPQ());
|
||||
if (res_pq_data.vnonce == httpNonce) {
|
||||
if (status == WaitingBoth) {
|
||||
status = HttpReady;
|
||||
httpStartTimer.start(MTPTcpConnectionWaitTimeout);
|
||||
} else {
|
||||
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by pq-response, awaited").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
status = UsingHttp;
|
||||
sock.disconnectFromHost();
|
||||
emit connected();
|
||||
}
|
||||
}
|
||||
} catch (Exception &e) {
|
||||
DEBUG_LOG(("Connection Error: exception in parsing HTTP fake pq-responce, %1").arg(e.what()));
|
||||
if (status == WaitingBoth) {
|
||||
status = WaitingTcp;
|
||||
} else {
|
||||
emit error();
|
||||
}
|
||||
}
|
||||
} else if (status == UsingTcp) {
|
||||
DEBUG_LOG(("Connection Info: already using tcp, ignoring http response"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!requests.remove(reply)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool mayBeBadKey = HTTPConnection::handleError(reply) && _sentEncrypted;
|
||||
if (status == WaitingBoth) {
|
||||
status = WaitingTcp;
|
||||
} else if (status == WaitingHttp || status == UsingHttp) {
|
||||
emit error(mayBeBadKey);
|
||||
} else {
|
||||
LOG(("Strange Http Error: status %1").arg(status));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AutoConnection::socketPacket(const char *packet, uint32 length) {
|
||||
if (status == FinishedWork) return;
|
||||
|
||||
mtpBuffer data = AbstractTCPConnection::handleResponse(packet, length);
|
||||
if (data.size() == 1) {
|
||||
if (status == WaitingBoth) {
|
||||
status = WaitingHttp;
|
||||
sock.disconnectFromHost();
|
||||
} else if (status == HttpReady) {
|
||||
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by bad tcp response, ready").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
status = UsingHttp;
|
||||
sock.disconnectFromHost();
|
||||
emit connected();
|
||||
} else if (status == WaitingTcp || status == UsingTcp) {
|
||||
bool mayBeBadKey = (data[0] == -410) && _sentEncrypted;
|
||||
emit error(mayBeBadKey);
|
||||
} else {
|
||||
LOG(("Strange Tcp Error; status %1").arg(status));
|
||||
}
|
||||
} else if (status == UsingTcp) {
|
||||
receivedQueue.push_back(data);
|
||||
emit receivedData();
|
||||
} else if (status == WaitingBoth || status == WaitingTcp || status == HttpReady) {
|
||||
tcpTimeoutTimer.stop();
|
||||
try {
|
||||
MTPResPQ res_pq = readPQFakeReply(data);
|
||||
const MTPDresPQ &res_pq_data(res_pq.c_resPQ());
|
||||
if (res_pq_data.vnonce == tcpNonce) {
|
||||
DEBUG_LOG(("Connection Info: TCP/%1-transport chosen by pq-response").arg((_flagsTcp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
status = UsingTcp;
|
||||
emit connected();
|
||||
}
|
||||
} catch (Exception &e) {
|
||||
DEBUG_LOG(("Connection Error: exception in parsing TCP fake pq-responce, %1").arg(e.what()));
|
||||
if (status == WaitingBoth) {
|
||||
status = WaitingHttp;
|
||||
sock.disconnectFromHost();
|
||||
} else if (status == HttpReady) {
|
||||
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by bad tcp response, awaited").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
status = UsingHttp;
|
||||
sock.disconnectFromHost();
|
||||
emit connected();
|
||||
} else {
|
||||
emit error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AutoConnection::usingHttpWait() {
|
||||
return (status == UsingHttp);
|
||||
}
|
||||
|
||||
bool AutoConnection::needHttpWait() {
|
||||
return (status == UsingHttp) ? requests.isEmpty() : false;
|
||||
}
|
||||
|
||||
int32 AutoConnection::debugState() const {
|
||||
return (status == UsingHttp) ? -1 : (UsingTcp ? sock.state() : -777);
|
||||
}
|
||||
|
||||
QString AutoConnection::transport() const {
|
||||
if (status == UsingTcp) {
|
||||
return qsl("TCP");
|
||||
} else if (status == UsingHttp) {
|
||||
return qsl("HTTP");
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
void AutoConnection::socketError(QAbstractSocket::SocketError e) {
|
||||
if (status == FinishedWork) return;
|
||||
|
||||
AbstractTCPConnection::handleError(e, sock);
|
||||
if (status == WaitingBoth) {
|
||||
status = WaitingHttp;
|
||||
} else if (status == HttpReady) {
|
||||
DEBUG_LOG(("Connection Info: HTTP/%1-transport chosen by tcp error, ready").arg((_flagsHttp & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
status = UsingHttp;
|
||||
emit connected();
|
||||
} else if (status == WaitingTcp || status == UsingTcp) {
|
||||
emit error();
|
||||
} else {
|
||||
LOG(("Strange Tcp Error: status %1").arg(status));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
94
Telegram/SourceFiles/mtproto/connection_auto.h
Normal file
94
Telegram/SourceFiles/mtproto/connection_auto.h
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/connection_tcp.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
class AutoConnection : public AbstractTCPConnection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AutoConnection(QThread *thread);
|
||||
|
||||
void sendData(mtpBuffer &buffer) override;
|
||||
void disconnectFromServer() override;
|
||||
void connectTcp(const QString &addr, int32 port, MTPDdcOption::Flags flags) override;
|
||||
void connectHttp(const QString &addr, int32 port, MTPDdcOption::Flags flags) override;
|
||||
bool isConnected() const override;
|
||||
bool usingHttpWait() override;
|
||||
bool needHttpWait() override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
QString transport() const override;
|
||||
|
||||
public slots:
|
||||
|
||||
void socketError(QAbstractSocket::SocketError e);
|
||||
void requestFinished(QNetworkReply *reply);
|
||||
|
||||
void onSocketConnected();
|
||||
void onSocketDisconnected();
|
||||
void onHttpStart();
|
||||
|
||||
void onTcpTimeoutTimer();
|
||||
|
||||
protected:
|
||||
|
||||
void socketPacket(const char *packet, uint32 length) override;
|
||||
|
||||
private:
|
||||
|
||||
void httpSend(mtpBuffer &buffer);
|
||||
enum Status {
|
||||
WaitingBoth = 0,
|
||||
WaitingHttp,
|
||||
WaitingTcp,
|
||||
HttpReady,
|
||||
UsingHttp,
|
||||
UsingTcp,
|
||||
FinishedWork
|
||||
};
|
||||
Status status;
|
||||
MTPint128 tcpNonce, httpNonce;
|
||||
QTimer httpStartTimer;
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
QUrl address;
|
||||
|
||||
typedef QSet<QNetworkReply*> Requests;
|
||||
Requests requests;
|
||||
|
||||
QString _addrTcp, _addrHttp;
|
||||
int32 _portTcp, _portHttp;
|
||||
MTPDdcOption::Flags _flagsTcp, _flagsHttp;
|
||||
int32 _tcpTimeout;
|
||||
QTimer tcpTimeoutTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
218
Telegram/SourceFiles/mtproto/connection_http.cpp
Normal file
218
Telegram/SourceFiles/mtproto/connection_http.cpp
Normal file
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "mtproto/connection_http.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
mtpBuffer HTTPConnection::handleResponse(QNetworkReply *reply) {
|
||||
QByteArray response = reply->readAll();
|
||||
TCP_LOG(("HTTP Info: read %1 bytes").arg(response.size()));
|
||||
|
||||
if (response.isEmpty()) return mtpBuffer();
|
||||
|
||||
if (response.size() & 0x03 || response.size() < 8) {
|
||||
LOG(("HTTP Error: bad response size %1").arg(response.size()));
|
||||
return mtpBuffer(1, -500);
|
||||
}
|
||||
|
||||
mtpBuffer data(response.size() >> 2);
|
||||
memcpy(data.data(), response.constData(), response.size());
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool HTTPConnection::handleError(QNetworkReply *reply) { // returnes "maybe bad key"
|
||||
bool mayBeBadKey = false;
|
||||
|
||||
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
if (statusCode.isValid()) {
|
||||
int status = statusCode.toInt();
|
||||
mayBeBadKey = (status == 410);
|
||||
if (status == 429) {
|
||||
LOG(("Protocol Error: 429 flood code returned!"));
|
||||
}
|
||||
}
|
||||
|
||||
switch (reply->error()) {
|
||||
case QNetworkReply::ConnectionRefusedError: LOG(("HTTP Error: connection refused - %1").arg(reply->errorString())); break;
|
||||
case QNetworkReply::RemoteHostClosedError: LOG(("HTTP Error: remote host closed - %1").arg(reply->errorString())); break;
|
||||
case QNetworkReply::HostNotFoundError: LOG(("HTTP Error: host not found - %2").arg(reply->error()).arg(reply->errorString())); break;
|
||||
case QNetworkReply::TimeoutError: LOG(("HTTP Error: timeout - %2").arg(reply->error()).arg(reply->errorString())); break;
|
||||
case QNetworkReply::OperationCanceledError: LOG(("HTTP Error: cancelled - %2").arg(reply->error()).arg(reply->errorString())); break;
|
||||
case QNetworkReply::SslHandshakeFailedError:
|
||||
case QNetworkReply::TemporaryNetworkFailureError:
|
||||
case QNetworkReply::NetworkSessionFailedError:
|
||||
case QNetworkReply::BackgroundRequestNotAllowedError:
|
||||
case QNetworkReply::UnknownNetworkError: LOG(("HTTP Error: network error %1 - %2").arg(reply->error()).arg(reply->errorString())); break;
|
||||
|
||||
// proxy errors (101-199):
|
||||
case QNetworkReply::ProxyConnectionRefusedError:
|
||||
case QNetworkReply::ProxyConnectionClosedError:
|
||||
case QNetworkReply::ProxyNotFoundError:
|
||||
case QNetworkReply::ProxyTimeoutError:
|
||||
case QNetworkReply::ProxyAuthenticationRequiredError:
|
||||
case QNetworkReply::UnknownProxyError:LOG(("HTTP Error: proxy error %1 - %2").arg(reply->error()).arg(reply->errorString())); break;
|
||||
|
||||
// content errors (201-299):
|
||||
case QNetworkReply::ContentAccessDenied:
|
||||
case QNetworkReply::ContentOperationNotPermittedError:
|
||||
case QNetworkReply::ContentNotFoundError:
|
||||
case QNetworkReply::AuthenticationRequiredError:
|
||||
case QNetworkReply::ContentReSendError:
|
||||
case QNetworkReply::UnknownContentError: LOG(("HTTP Error: content error %1 - %2").arg(reply->error()).arg(reply->errorString())); break;
|
||||
|
||||
// protocol errors
|
||||
case QNetworkReply::ProtocolUnknownError:
|
||||
case QNetworkReply::ProtocolInvalidOperationError:
|
||||
case QNetworkReply::ProtocolFailure: LOG(("HTTP Error: protocol error %1 - %2").arg(reply->error()).arg(reply->errorString())); break;
|
||||
};
|
||||
TCP_LOG(("HTTP Error %1, restarting! - %2").arg(reply->error()).arg(reply->errorString()));
|
||||
|
||||
return mayBeBadKey;
|
||||
}
|
||||
|
||||
HTTPConnection::HTTPConnection(QThread *thread) : AbstractConnection(thread)
|
||||
, status(WaitingHttp)
|
||||
, httpNonce(rand_value<MTPint128>())
|
||||
, _flags(0) {
|
||||
manager.moveToThread(thread);
|
||||
App::setProxySettings(manager);
|
||||
}
|
||||
|
||||
void HTTPConnection::sendData(mtpBuffer &buffer) {
|
||||
if (status == FinishedWork) return;
|
||||
|
||||
if (buffer.size() < 3) {
|
||||
LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime)));
|
||||
TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str()));
|
||||
emit error();
|
||||
return;
|
||||
}
|
||||
|
||||
int32 requestSize = (buffer.size() - 3) * sizeof(mtpPrime);
|
||||
|
||||
QNetworkRequest request(address);
|
||||
request.setHeader(QNetworkRequest::ContentLengthHeader, QVariant(requestSize));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant(qsl("application/x-www-form-urlencoded")));
|
||||
|
||||
TCP_LOG(("HTTP Info: sending %1 len request %2").arg(requestSize).arg(Logs::mb(&buffer[2], requestSize).str()));
|
||||
requests.insert(manager.post(request, QByteArray((const char*)(&buffer[2]), requestSize)));
|
||||
}
|
||||
|
||||
void HTTPConnection::disconnectFromServer() {
|
||||
if (status == FinishedWork) return;
|
||||
status = FinishedWork;
|
||||
|
||||
Requests copy = requests;
|
||||
requests.clear();
|
||||
for (Requests::const_iterator i = copy.cbegin(), e = copy.cend(); i != e; ++i) {
|
||||
(*i)->abort();
|
||||
(*i)->deleteLater();
|
||||
}
|
||||
|
||||
disconnect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
|
||||
|
||||
address = QUrl();
|
||||
}
|
||||
|
||||
void HTTPConnection::connectHttp(const QString &addr, int32 p, MTPDdcOption::Flags flags) {
|
||||
address = QUrl(((flags & MTPDdcOption::Flag::f_ipv6) ? qsl("http://[%1]:%2/api") : qsl("http://%1:%2/api")).arg(addr).arg(80));//not p - always 80 port for http transport
|
||||
TCP_LOG(("HTTP Info: address is %1").arg(address.toDisplayString()));
|
||||
connect(&manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
|
||||
|
||||
_flags = flags;
|
||||
|
||||
mtpBuffer buffer(preparePQFake(httpNonce));
|
||||
|
||||
DEBUG_LOG(("Connection Info: sending fake req_pq through HTTP/%1 transport").arg((flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
|
||||
sendData(buffer);
|
||||
}
|
||||
|
||||
bool HTTPConnection::isConnected() const {
|
||||
return (status == UsingHttp);
|
||||
}
|
||||
|
||||
void HTTPConnection::requestFinished(QNetworkReply *reply) {
|
||||
if (status == FinishedWork) return;
|
||||
|
||||
reply->deleteLater();
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
requests.remove(reply);
|
||||
|
||||
mtpBuffer data = handleResponse(reply);
|
||||
if (data.size() == 1) {
|
||||
emit error();
|
||||
} else if (!data.isEmpty()) {
|
||||
if (status == UsingHttp) {
|
||||
receivedQueue.push_back(data);
|
||||
emit receivedData();
|
||||
} else {
|
||||
try {
|
||||
MTPResPQ res_pq = readPQFakeReply(data);
|
||||
const MTPDresPQ &res_pq_data(res_pq.c_resPQ());
|
||||
if (res_pq_data.vnonce == httpNonce) {
|
||||
DEBUG_LOG(("Connection Info: HTTP/%1-transport connected by pq-response").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
status = UsingHttp;
|
||||
emit connected();
|
||||
}
|
||||
} catch (Exception &e) {
|
||||
DEBUG_LOG(("Connection Error: exception in parsing HTTP fake pq-responce, %1").arg(e.what()));
|
||||
emit error();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!requests.remove(reply)) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool mayBeBadKey = handleError(reply) && _sentEncrypted;
|
||||
|
||||
emit error(mayBeBadKey);
|
||||
}
|
||||
}
|
||||
|
||||
bool HTTPConnection::usingHttpWait() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HTTPConnection::needHttpWait() {
|
||||
return requests.isEmpty();
|
||||
}
|
||||
|
||||
int32 HTTPConnection::debugState() const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString HTTPConnection::transport() const {
|
||||
if (status == UsingHttp) {
|
||||
return qsl("HTTP");
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
76
Telegram/SourceFiles/mtproto/connection_http.h
Normal file
76
Telegram/SourceFiles/mtproto/connection_http.h
Normal file
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/connection_abstract.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
class HTTPConnection : public AbstractConnection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
HTTPConnection(QThread *thread);
|
||||
|
||||
void sendData(mtpBuffer &buffer) override;
|
||||
void disconnectFromServer() override;
|
||||
void connectTcp(const QString &addr, int32 port, MTPDdcOption::Flags flags) override { // not supported
|
||||
}
|
||||
void connectHttp(const QString &addr, int32 port, MTPDdcOption::Flags flags) override;
|
||||
bool isConnected() const override;
|
||||
bool usingHttpWait() override;
|
||||
bool needHttpWait() override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
QString transport() const override;
|
||||
|
||||
public slots:
|
||||
|
||||
void requestFinished(QNetworkReply *reply);
|
||||
|
||||
static mtpBuffer handleResponse(QNetworkReply *reply);
|
||||
static bool handleError(QNetworkReply *reply); // returnes "maybe bad key"
|
||||
|
||||
private:
|
||||
|
||||
enum Status {
|
||||
WaitingHttp = 0,
|
||||
UsingHttp,
|
||||
FinishedWork
|
||||
};
|
||||
Status status;
|
||||
MTPint128 httpNonce;
|
||||
MTPDdcOption::Flags _flags;
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
QUrl address;
|
||||
|
||||
typedef QSet<QNetworkReply*> Requests;
|
||||
Requests requests;
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
398
Telegram/SourceFiles/mtproto/connection_tcp.cpp
Normal file
398
Telegram/SourceFiles/mtproto/connection_tcp.cpp
Normal file
|
@ -0,0 +1,398 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "mtproto/connection_tcp.h"
|
||||
|
||||
#include <openssl/aes.h>
|
||||
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
namespace {
|
||||
|
||||
uint32 tcpPacketSize(const char *packet) { // must have at least 4 bytes readable
|
||||
uint32 result = (packet[0] > 0) ? packet[0] : 0;
|
||||
if (result == 0x7f) {
|
||||
const uchar *bytes = reinterpret_cast<const uchar*>(packet);
|
||||
result = (((uint32(bytes[3]) << 8) | uint32(bytes[2])) << 8) | uint32(bytes[1]);
|
||||
return (result << 2) + 4;
|
||||
}
|
||||
return (result << 2) + 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AbstractTCPConnection::AbstractTCPConnection(QThread *thread) : AbstractConnection(thread)
|
||||
, packetNum(0)
|
||||
, packetRead(0)
|
||||
, packetLeft(0)
|
||||
, readingToShort(true)
|
||||
, currentPos((char*)shortBuffer) {
|
||||
}
|
||||
|
||||
AbstractTCPConnection::~AbstractTCPConnection() {
|
||||
}
|
||||
|
||||
void AbstractTCPConnection::socketRead() {
|
||||
if (sock.state() != QAbstractSocket::ConnectedState) {
|
||||
LOG(("MTP error: socket not connected in socketRead(), state: %1").arg(sock.state()));
|
||||
emit error();
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
uint32 toRead = packetLeft ? packetLeft : (readingToShort ? (MTPShortBufferSize * sizeof(mtpPrime) - packetRead) : 4);
|
||||
if (readingToShort) {
|
||||
if (currentPos + toRead > ((char*)shortBuffer) + MTPShortBufferSize * sizeof(mtpPrime)) {
|
||||
longBuffer.resize(((packetRead + toRead) >> 2) + 1);
|
||||
memcpy(&longBuffer[0], shortBuffer, packetRead);
|
||||
currentPos = ((char*)&longBuffer[0]) + packetRead;
|
||||
readingToShort = false;
|
||||
}
|
||||
} else {
|
||||
if (longBuffer.size() * sizeof(mtpPrime) < packetRead + toRead) {
|
||||
longBuffer.resize(((packetRead + toRead) >> 2) + 1);
|
||||
currentPos = ((char*)&longBuffer[0]) + packetRead;
|
||||
}
|
||||
}
|
||||
int32 bytes = (int32)sock.read(currentPos, toRead);
|
||||
if (bytes > 0) {
|
||||
aesCtrEncrypt(currentPos, bytes, _receiveKey, &_receiveState);
|
||||
TCP_LOG(("TCP Info: read %1 bytes").arg(bytes));
|
||||
|
||||
packetRead += bytes;
|
||||
currentPos += bytes;
|
||||
if (packetLeft) {
|
||||
packetLeft -= bytes;
|
||||
if (!packetLeft) {
|
||||
socketPacket(currentPos - packetRead, packetRead);
|
||||
currentPos = (char*)shortBuffer;
|
||||
packetRead = packetLeft = 0;
|
||||
readingToShort = true;
|
||||
longBuffer.clear();
|
||||
} else {
|
||||
TCP_LOG(("TCP Info: not enough %1 for packet! read %2").arg(packetLeft).arg(packetRead));
|
||||
emit receivedSome();
|
||||
}
|
||||
} else {
|
||||
bool move = false;
|
||||
while (packetRead >= 4) {
|
||||
uint32 packetSize = tcpPacketSize(currentPos - packetRead);
|
||||
if (packetSize < 5 || packetSize > MTPPacketSizeMax) {
|
||||
LOG(("TCP Error: packet size = %1").arg(packetSize));
|
||||
emit error();
|
||||
return;
|
||||
}
|
||||
if (packetRead >= packetSize) {
|
||||
socketPacket(currentPos - packetRead, packetSize);
|
||||
packetRead -= packetSize;
|
||||
packetLeft = 0;
|
||||
move = true;
|
||||
} else {
|
||||
packetLeft = packetSize - packetRead;
|
||||
TCP_LOG(("TCP Info: not enough %1 for packet! size %2 read %3").arg(packetLeft).arg(packetSize).arg(packetRead));
|
||||
emit receivedSome();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (move) {
|
||||
if (!packetRead) {
|
||||
currentPos = (char*)shortBuffer;
|
||||
readingToShort = true;
|
||||
longBuffer.clear();
|
||||
} else if (!readingToShort && packetRead < MTPShortBufferSize * sizeof(mtpPrime)) {
|
||||
memcpy(shortBuffer, currentPos - packetRead, packetRead);
|
||||
currentPos = (char*)shortBuffer + packetRead;
|
||||
readingToShort = true;
|
||||
longBuffer.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (bytes < 0) {
|
||||
LOG(("TCP Error: socket read return -1"));
|
||||
emit error();
|
||||
return;
|
||||
} else {
|
||||
TCP_LOG(("TCP Info: no bytes read, but bytes available was true..."));
|
||||
break;
|
||||
}
|
||||
} while (sock.state() == QAbstractSocket::ConnectedState && sock.bytesAvailable());
|
||||
}
|
||||
|
||||
mtpBuffer AbstractTCPConnection::handleResponse(const char *packet, uint32 length) {
|
||||
if (length < 5 || length > MTPPacketSizeMax) {
|
||||
LOG(("TCP Error: bad packet size %1").arg(length));
|
||||
return mtpBuffer(1, -500);
|
||||
}
|
||||
int32 size = packet[0], len = length - 1;
|
||||
if (size == 0x7f) {
|
||||
const uchar *bytes = reinterpret_cast<const uchar*>(packet);
|
||||
size = (((uint32(bytes[3]) << 8) | uint32(bytes[2])) << 8) | uint32(bytes[1]);
|
||||
len -= 3;
|
||||
}
|
||||
if (size * int32(sizeof(mtpPrime)) != len) {
|
||||
LOG(("TCP Error: bad packet header"));
|
||||
TCP_LOG(("TCP Error: bad packet header, packet: %1").arg(Logs::mb(packet, length).str()));
|
||||
return mtpBuffer(1, -500);
|
||||
}
|
||||
const mtpPrime *packetdata = reinterpret_cast<const mtpPrime*>(packet + (length - len));
|
||||
TCP_LOG(("TCP Info: packet received, size = %1").arg(size * sizeof(mtpPrime)));
|
||||
if (size == 1) {
|
||||
if (*packetdata == -429) {
|
||||
LOG(("Protocol Error: -429 flood code returned!"));
|
||||
} else {
|
||||
LOG(("TCP Error: error packet received, code = %1").arg(*packetdata));
|
||||
}
|
||||
return mtpBuffer(1, *packetdata);
|
||||
}
|
||||
|
||||
mtpBuffer data(size);
|
||||
memcpy(data.data(), packetdata, size * sizeof(mtpPrime));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void AbstractTCPConnection::handleError(QAbstractSocket::SocketError e, QTcpSocket &sock) {
|
||||
switch (e) {
|
||||
case QAbstractSocket::ConnectionRefusedError:
|
||||
LOG(("TCP Error: socket connection refused - %1").arg(sock.errorString()));
|
||||
break;
|
||||
|
||||
case QAbstractSocket::RemoteHostClosedError:
|
||||
TCP_LOG(("TCP Info: remote host closed socket connection - %1").arg(sock.errorString()));
|
||||
break;
|
||||
|
||||
case QAbstractSocket::HostNotFoundError:
|
||||
LOG(("TCP Error: host not found - %1").arg(sock.errorString()));
|
||||
break;
|
||||
|
||||
case QAbstractSocket::SocketTimeoutError:
|
||||
LOG(("TCP Error: socket timeout - %1").arg(sock.errorString()));
|
||||
break;
|
||||
|
||||
case QAbstractSocket::NetworkError:
|
||||
LOG(("TCP Error: network - %1").arg(sock.errorString()));
|
||||
break;
|
||||
|
||||
case QAbstractSocket::ProxyAuthenticationRequiredError:
|
||||
case QAbstractSocket::ProxyConnectionRefusedError:
|
||||
case QAbstractSocket::ProxyConnectionClosedError:
|
||||
case QAbstractSocket::ProxyConnectionTimeoutError:
|
||||
case QAbstractSocket::ProxyNotFoundError:
|
||||
case QAbstractSocket::ProxyProtocolError:
|
||||
LOG(("TCP Error: proxy (%1) - %2").arg(e).arg(sock.errorString()));
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG(("TCP Error: other (%1) - %2").arg(e).arg(sock.errorString()));
|
||||
break;
|
||||
}
|
||||
|
||||
TCP_LOG(("TCP Error %1, restarting! - %2").arg(e).arg(sock.errorString()));
|
||||
}
|
||||
|
||||
TCPConnection::TCPConnection(QThread *thread) : AbstractTCPConnection(thread)
|
||||
, status(WaitingTcp)
|
||||
, tcpNonce(rand_value<MTPint128>())
|
||||
, _tcpTimeout(MTPMinReceiveDelay)
|
||||
, _flags(0) {
|
||||
tcpTimeoutTimer.moveToThread(thread);
|
||||
tcpTimeoutTimer.setSingleShot(true);
|
||||
connect(&tcpTimeoutTimer, SIGNAL(timeout()), this, SLOT(onTcpTimeoutTimer()));
|
||||
|
||||
sock.moveToThread(thread);
|
||||
App::setProxySettings(sock);
|
||||
connect(&sock, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
|
||||
connect(&sock, SIGNAL(connected()), this, SLOT(onSocketConnected()));
|
||||
connect(&sock, SIGNAL(disconnected()), this, SLOT(onSocketDisconnected()));
|
||||
}
|
||||
|
||||
void TCPConnection::onSocketConnected() {
|
||||
if (status == WaitingTcp) {
|
||||
mtpBuffer buffer(preparePQFake(tcpNonce));
|
||||
|
||||
DEBUG_LOG(("Connection Info: sending fake req_pq through TCP/%1 transport").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
|
||||
if (_tcpTimeout < 0) _tcpTimeout = -_tcpTimeout;
|
||||
tcpTimeoutTimer.start(_tcpTimeout);
|
||||
|
||||
sendData(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void TCPConnection::onTcpTimeoutTimer() {
|
||||
if (status == WaitingTcp) {
|
||||
if (_tcpTimeout < MTPMaxReceiveDelay) _tcpTimeout *= 2;
|
||||
_tcpTimeout = -_tcpTimeout;
|
||||
|
||||
QAbstractSocket::SocketState state = sock.state();
|
||||
if (state == QAbstractSocket::ConnectedState || state == QAbstractSocket::ConnectingState || state == QAbstractSocket::HostLookupState) {
|
||||
sock.disconnectFromHost();
|
||||
} else if (state != QAbstractSocket::ClosingState) {
|
||||
sock.connectToHost(QHostAddress(_addr), _port);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TCPConnection::onSocketDisconnected() {
|
||||
if (_tcpTimeout < 0) {
|
||||
_tcpTimeout = -_tcpTimeout;
|
||||
if (status == WaitingTcp) {
|
||||
sock.connectToHost(QHostAddress(_addr), _port);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (status == WaitingTcp || status == UsingTcp) {
|
||||
emit disconnected();
|
||||
}
|
||||
}
|
||||
|
||||
void TCPConnection::sendData(mtpBuffer &buffer) {
|
||||
if (status == FinishedWork) return;
|
||||
|
||||
if (buffer.size() < 3) {
|
||||
LOG(("TCP Error: writing bad packet, len = %1").arg(buffer.size() * sizeof(mtpPrime)));
|
||||
TCP_LOG(("TCP Error: bad packet %1").arg(Logs::mb(&buffer[0], buffer.size() * sizeof(mtpPrime)).str()));
|
||||
emit error();
|
||||
return;
|
||||
}
|
||||
|
||||
tcpSend(buffer);
|
||||
}
|
||||
|
||||
void AbstractTCPConnection::tcpSend(mtpBuffer &buffer) {
|
||||
if (!packetNum) {
|
||||
// prepare random part
|
||||
char nonce[64];
|
||||
uint32 *first = reinterpret_cast<uint32*>(nonce), *second = first + 1;
|
||||
uint32 first1 = 0x44414548U, first2 = 0x54534f50U, first3 = 0x20544547U, first4 = 0x20544547U, first5 = 0xeeeeeeeeU;
|
||||
uint32 second1 = 0;
|
||||
do {
|
||||
memset_rand(nonce, sizeof(nonce));
|
||||
} while (*first == first1 || *first == first2 || *first == first3 || *first == first4 || *first == first5 || *second == second1 || *reinterpret_cast<uchar*>(nonce) == 0xef);
|
||||
//sock.write(nonce, 64);
|
||||
|
||||
// prepare encryption key/iv
|
||||
memcpy(_sendKey, nonce + 8, CTRState::KeySize);
|
||||
memcpy(_sendState.ivec, nonce + 8 + CTRState::KeySize, CTRState::IvecSize);
|
||||
|
||||
// prepare decryption key/iv
|
||||
char reversed[48];
|
||||
memcpy(reversed, nonce + 8, sizeof(reversed));
|
||||
std::reverse(reversed, reversed + arraysize(reversed));
|
||||
memcpy(_receiveKey, reversed, CTRState::KeySize);
|
||||
memcpy(_receiveState.ivec, reversed + CTRState::KeySize, CTRState::IvecSize);
|
||||
|
||||
// write protocol identifier
|
||||
*reinterpret_cast<uint32*>(nonce + 56) = 0xefefefefU;
|
||||
|
||||
sock.write(nonce, 56);
|
||||
aesCtrEncrypt(nonce, 64, _sendKey, &_sendState);
|
||||
sock.write(nonce + 56, 8);
|
||||
}
|
||||
++packetNum;
|
||||
|
||||
uint32 size = buffer.size() - 3, len = size * 4;
|
||||
char *data = reinterpret_cast<char*>(&buffer[0]);
|
||||
if (size < 0x7f) {
|
||||
data[7] = char(size);
|
||||
TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 1));
|
||||
|
||||
aesCtrEncrypt(data + 7, len + 1, _sendKey, &_sendState);
|
||||
sock.write(data + 7, len + 1);
|
||||
} else {
|
||||
data[4] = 0x7f;
|
||||
reinterpret_cast<uchar*>(data)[5] = uchar(size & 0xFF);
|
||||
reinterpret_cast<uchar*>(data)[6] = uchar((size >> 8) & 0xFF);
|
||||
reinterpret_cast<uchar*>(data)[7] = uchar((size >> 16) & 0xFF);
|
||||
TCP_LOG(("TCP Info: write %1 packet %2").arg(packetNum).arg(len + 4));
|
||||
|
||||
aesCtrEncrypt(data + 4, len + 4, _sendKey, &_sendState);
|
||||
sock.write(data + 4, len + 4);
|
||||
}
|
||||
}
|
||||
|
||||
void TCPConnection::disconnectFromServer() {
|
||||
if (status == FinishedWork) return;
|
||||
status = FinishedWork;
|
||||
|
||||
disconnect(&sock, SIGNAL(readyRead()), 0, 0);
|
||||
sock.close();
|
||||
}
|
||||
|
||||
void TCPConnection::connectTcp(const QString &addr, int32 port, MTPDdcOption::Flags flags) {
|
||||
_addr = addr;
|
||||
_port = port;
|
||||
_flags = flags;
|
||||
|
||||
connect(&sock, SIGNAL(readyRead()), this, SLOT(socketRead()));
|
||||
sock.connectToHost(QHostAddress(_addr), _port);
|
||||
}
|
||||
|
||||
void TCPConnection::socketPacket(const char *packet, uint32 length) {
|
||||
if (status == FinishedWork) return;
|
||||
|
||||
mtpBuffer data = handleResponse(packet, length);
|
||||
if (data.size() == 1) {
|
||||
bool mayBeBadKey = (data[0] == -410) && _sentEncrypted;
|
||||
emit error(mayBeBadKey);
|
||||
} else if (status == UsingTcp) {
|
||||
receivedQueue.push_back(data);
|
||||
emit receivedData();
|
||||
} else if (status == WaitingTcp) {
|
||||
tcpTimeoutTimer.stop();
|
||||
try {
|
||||
MTPResPQ res_pq = readPQFakeReply(data);
|
||||
const MTPDresPQ &res_pq_data(res_pq.c_resPQ());
|
||||
if (res_pq_data.vnonce == tcpNonce) {
|
||||
DEBUG_LOG(("Connection Info: TCP/%1-transport chosen by pq-response").arg((_flags & MTPDdcOption::Flag::f_ipv6) ? "IPv6" : "IPv4"));
|
||||
status = UsingTcp;
|
||||
emit connected();
|
||||
}
|
||||
} catch (Exception &e) {
|
||||
DEBUG_LOG(("Connection Error: exception in parsing TCP fake pq-responce, %1").arg(e.what()));
|
||||
emit error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TCPConnection::isConnected() const {
|
||||
return (status == UsingTcp);
|
||||
}
|
||||
|
||||
int32 TCPConnection::debugState() const {
|
||||
return sock.state();
|
||||
}
|
||||
|
||||
QString TCPConnection::transport() const {
|
||||
return isConnected() ? qsl("TCP") : QString();
|
||||
}
|
||||
|
||||
void TCPConnection::socketError(QAbstractSocket::SocketError e) {
|
||||
if (status == FinishedWork) return;
|
||||
|
||||
handleError(e, sock);
|
||||
emit error();
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
118
Telegram/SourceFiles/mtproto/connection_tcp.h
Normal file
118
Telegram/SourceFiles/mtproto/connection_tcp.h
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/auth_key.h"
|
||||
#include "mtproto/connection_abstract.h"
|
||||
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
class AbstractTCPConnection : public AbstractConnection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AbstractTCPConnection(QThread *thread);
|
||||
virtual ~AbstractTCPConnection() = 0;
|
||||
|
||||
public slots:
|
||||
|
||||
void socketRead();
|
||||
|
||||
protected:
|
||||
|
||||
QTcpSocket sock;
|
||||
uint32 packetNum; // sent packet number
|
||||
|
||||
uint32 packetRead, packetLeft; // reading from socket
|
||||
bool readingToShort;
|
||||
char *currentPos;
|
||||
mtpBuffer longBuffer;
|
||||
mtpPrime shortBuffer[MTPShortBufferSize];
|
||||
virtual void socketPacket(const char *packet, uint32 length) = 0;
|
||||
|
||||
static mtpBuffer handleResponse(const char *packet, uint32 length);
|
||||
static void handleError(QAbstractSocket::SocketError e, QTcpSocket &sock);
|
||||
static uint32 fourCharsToUInt(char ch1, char ch2, char ch3, char ch4) {
|
||||
char ch[4] = { ch1, ch2, ch3, ch4 };
|
||||
return *reinterpret_cast<uint32*>(ch);
|
||||
}
|
||||
|
||||
void tcpSend(mtpBuffer &buffer);
|
||||
uchar _sendKey[CTRState::KeySize];
|
||||
CTRState _sendState;
|
||||
uchar _receiveKey[CTRState::KeySize];
|
||||
CTRState _receiveState;
|
||||
|
||||
};
|
||||
|
||||
class TCPConnection : public AbstractTCPConnection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
TCPConnection(QThread *thread);
|
||||
|
||||
void sendData(mtpBuffer &buffer) override;
|
||||
void disconnectFromServer() override;
|
||||
void connectTcp(const QString &addr, int32 port, MTPDdcOption::Flags flags) override;
|
||||
void connectHttp(const QString &addr, int32 port, MTPDdcOption::Flags flags) override { // not supported
|
||||
}
|
||||
bool isConnected() const override;
|
||||
|
||||
int32 debugState() const override;
|
||||
|
||||
QString transport() const override;
|
||||
|
||||
public slots:
|
||||
|
||||
void socketError(QAbstractSocket::SocketError e);
|
||||
|
||||
void onSocketConnected();
|
||||
void onSocketDisconnected();
|
||||
|
||||
void onTcpTimeoutTimer();
|
||||
|
||||
protected:
|
||||
|
||||
void socketPacket(const char *packet, uint32 length) override;
|
||||
|
||||
private:
|
||||
|
||||
enum Status {
|
||||
WaitingTcp = 0,
|
||||
UsingTcp,
|
||||
FinishedWork
|
||||
};
|
||||
Status status;
|
||||
MTPint128 tcpNonce;
|
||||
|
||||
QString _addr;
|
||||
int32 _port, _tcpTimeout;
|
||||
MTPDdcOption::Flags _flags;
|
||||
QTimer tcpTimeoutTimer;
|
||||
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
|
@ -19,7 +19,10 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "mtpCoreTypes.h"
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
|
||||
#include "zlib.h"
|
||||
|
||||
#include "lang.h"
|
||||
|
||||
|
@ -63,7 +66,7 @@ void mtpTextSerializeCore(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
|||
} else if (strUtf8.size() < 64) {
|
||||
to.add(Logs::mb(strUtf8.constData(), strUtf8.size()).str()).add(" [").add(mtpWrapNumber(strUtf8.size())).add(" BYTES]");
|
||||
} else {
|
||||
to.add(Logs::mb(strUtf8.constData(), 16).str()).add(".. [").add(mtpWrapNumber(strUtf8.size())).add(" BYTES]");
|
||||
to.add(Logs::mb(strUtf8.constData(), 16).str()).add("... [").add(mtpWrapNumber(strUtf8.size())).add(" BYTES]");
|
||||
}
|
||||
} break;
|
||||
|
||||
|
@ -149,13 +152,13 @@ void mtpTextSerializeCore(MTPStringLogger &to, const mtpPrime *&from, const mtpP
|
|||
}
|
||||
}
|
||||
|
||||
const MTPReplyMarkup MTPnullMarkup = MTP_replyKeyboardMarkup(MTP_int(0), MTP_vector<MTPKeyboardButtonRow>(0));
|
||||
const MTPReplyMarkup MTPnullMarkup = MTP_replyKeyboardMarkup(MTP_flags(MTPDreplyKeyboardMarkup::Flags(0)), MTP_vector<MTPKeyboardButtonRow>(0));
|
||||
const MTPVector<MTPMessageEntity> MTPnullEntities = MTP_vector<MTPMessageEntity>(0);
|
||||
const MTPMessageFwdHeader MTPnullFwdHeader = MTP_messageFwdHeader(MTPint(), MTPint(), MTPint(), MTPint(), MTPint());
|
||||
const MTPMessageFwdHeader MTPnullFwdHeader = MTP_messageFwdHeader(MTP_flags(MTPDmessageFwdHeader::Flags(0)), MTPint(), MTPint(), MTPint(), MTPint());
|
||||
|
||||
QString stickerSetTitle(const MTPDstickerSet &s) {
|
||||
QString title = qs(s.vtitle);
|
||||
if ((s.vflags.v & MTPDstickerSet::flag_official) && !title.compare(qstr("Great Minds"), Qt::CaseInsensitive)) {
|
||||
if ((s.vflags.v & MTPDstickerSet::Flag::f_official) && !title.compare(qstr("Great Minds"), Qt::CaseInsensitive)) {
|
||||
return lang(lng_stickers_default_set);
|
||||
}
|
||||
return title;
|
|
@ -21,37 +21,17 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include <zlib.h>
|
||||
|
||||
#undef min
|
||||
#undef max
|
||||
namespace MTP {
|
||||
|
||||
//#define DEBUG_MTPPRIME
|
||||
// type DcId represents actual data center id, while in most cases
|
||||
// we use some shifted ids, like DcId() + X * DCShift
|
||||
typedef int32 DcId;
|
||||
typedef int32 ShiftedDcId;
|
||||
|
||||
}
|
||||
|
||||
#ifdef DEBUG_MTPPRIME
|
||||
class mtpPrime { // for debug visualization, not like int32 :( in default constructor
|
||||
public:
|
||||
explicit mtpPrime() : _v(0) {
|
||||
}
|
||||
mtpPrime(int32 v) : _v(v) {
|
||||
}
|
||||
mtpPrime &operator=(int32 v) {
|
||||
_v = v;
|
||||
return (*this);
|
||||
}
|
||||
operator int32&() {
|
||||
return _v;
|
||||
}
|
||||
operator const int32 &() const {
|
||||
return _v;
|
||||
}
|
||||
private:
|
||||
int32 _v;
|
||||
};
|
||||
#else
|
||||
typedef int32 mtpPrime;
|
||||
#endif
|
||||
|
||||
typedef int32 mtpRequestId;
|
||||
typedef uint64 mtpMsgId;
|
||||
typedef uint64 mtpPingId;
|
||||
|
@ -346,6 +326,7 @@ enum {
|
|||
mtpc_gzip_packed = 0x3072cfa1
|
||||
};
|
||||
static const mtpTypeId mtpc_bytes = mtpc_string;
|
||||
static const mtpTypeId mtpc_flags = mtpc_int;
|
||||
static const mtpTypeId mtpc_core_message = -1; // undefined type, but is used
|
||||
static const mtpTypeId mtpLayers[] = {
|
||||
mtpTypeId(mtpc_invokeWithLayer1),
|
||||
|
@ -368,7 +349,7 @@ static const mtpTypeId mtpLayers[] = {
|
|||
mtpTypeId(mtpc_invokeWithLayer18),
|
||||
};
|
||||
static const uint32 mtpLayerMaxSingle = sizeof(mtpLayers) / sizeof(mtpLayers[0]);
|
||||
static const mtpPrime mtpCurrentLayer = 49;
|
||||
static const mtpPrime mtpCurrentLayer = 50;
|
||||
|
||||
template <typename bareT>
|
||||
class MTPBoxed : public bareT {
|
||||
|
@ -446,6 +427,49 @@ inline MTPint MTP_int(int32 v) {
|
|||
}
|
||||
typedef MTPBoxed<MTPint> MTPInt;
|
||||
|
||||
template <typename Flags>
|
||||
class MTPflags {
|
||||
public:
|
||||
Flags v;
|
||||
static_assert(sizeof(Flags) == sizeof(int32), "MTPflags are allowed only wrapping int32 flag types!");
|
||||
|
||||
MTPflags() {
|
||||
}
|
||||
MTPflags(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_flags) {
|
||||
read(from, end, cons);
|
||||
}
|
||||
|
||||
uint32 innerLength() const {
|
||||
return sizeof(Flags);
|
||||
}
|
||||
mtpTypeId type() const {
|
||||
return mtpc_flags;
|
||||
}
|
||||
void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_flags) {
|
||||
if (from + 1 > end) throw mtpErrorInsufficient();
|
||||
if (cons != mtpc_flags) throw mtpErrorUnexpected(cons, "MTPflags");
|
||||
v = static_cast<Flags>(*(from++));
|
||||
}
|
||||
void write(mtpBuffer &to) const {
|
||||
to.push_back(static_cast<mtpPrime>(v));
|
||||
}
|
||||
|
||||
private:
|
||||
explicit MTPflags(Flags val) : v(val) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
friend MTPflags<T> MTP_flags(T v);
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
inline MTPflags<T> MTP_flags(T v) {
|
||||
return MTPflags<T>(v);
|
||||
}
|
||||
|
||||
template <typename Flags>
|
||||
using MTPFlags = MTPBoxed<MTPflags<Flags>>;
|
||||
|
||||
inline bool operator==(const MTPint &a, const MTPint &b) {
|
||||
return a.v == b.v;
|
||||
}
|
||||
|
@ -787,19 +811,6 @@ public:
|
|||
VType v;
|
||||
};
|
||||
|
||||
|
||||
|
||||
template <typename T>
|
||||
class MTPvector;
|
||||
template <typename T>
|
||||
MTPvector<T> MTP_vector(uint32 count);
|
||||
|
||||
template <typename T>
|
||||
MTPvector<T> MTP_vector(uint32 count, const T &value);
|
||||
|
||||
template <typename T>
|
||||
MTPvector<T> MTP_vector(const QVector<T> &v);
|
||||
|
||||
template <typename T>
|
||||
class MTPvector : private mtpDataOwner {
|
||||
public:
|
||||
|
@ -853,9 +864,12 @@ private:
|
|||
explicit MTPvector(MTPDvector<T> *_data) : mtpDataOwner(_data) {
|
||||
}
|
||||
|
||||
friend MTPvector<T> MTP_vector<T>(uint32 count);
|
||||
friend MTPvector<T> MTP_vector<T>(uint32 count, const T &value);
|
||||
friend MTPvector<T> MTP_vector<T>(const QVector<T> &v);
|
||||
template <typename U>
|
||||
friend MTPvector<U> MTP_vector(uint32 count);
|
||||
template <typename U>
|
||||
friend MTPvector<U> MTP_vector(uint32 count, const U &value);
|
||||
template <typename U>
|
||||
friend MTPvector<U> MTP_vector(const QVector<U> &v);
|
||||
typedef typename MTPDvector<T>::VType VType;
|
||||
};
|
||||
template <typename T>
|
||||
|
@ -871,19 +885,7 @@ inline MTPvector<T> MTP_vector(const QVector<T> &v) {
|
|||
return MTPvector<T>(new MTPDvector<T>(v));
|
||||
}
|
||||
template <typename T>
|
||||
class MTPVector : public MTPBoxed<MTPvector<T> > {
|
||||
public:
|
||||
MTPVector() {
|
||||
}
|
||||
MTPVector(uint32 count) : MTPBoxed<MTPvector<T> >(MTP_vector<T>(count)) {
|
||||
}
|
||||
MTPVector(uint32 count, const T &value) : MTPBoxed<MTPvector<T> >(MTP_vector<T>(count, value)) {
|
||||
}
|
||||
MTPVector(const MTPvector<T> &v) : MTPBoxed<MTPvector<T> >(v) {
|
||||
}
|
||||
MTPVector(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = 0) : MTPBoxed<MTPvector<T> >(from, end, cons) {
|
||||
}
|
||||
};
|
||||
using MTPVector = MTPBoxed<MTPvector<T>>;
|
||||
|
||||
template <typename T>
|
||||
inline bool operator==(const MTPvector<T> &a, const MTPvector<T> &b) {
|
||||
|
@ -943,7 +945,7 @@ struct MTPStringLogger {
|
|||
char *b = new char[newsize];
|
||||
memcpy(b, p, size);
|
||||
alloced = newsize;
|
||||
delete p;
|
||||
delete[] p;
|
||||
p = b;
|
||||
}
|
||||
char *p;
|
||||
|
@ -964,7 +966,7 @@ inline QString mtpTextSerialize(const mtpPrime *&from, const mtpPrime *end) {
|
|||
return QString::fromUtf8(to.p, to.size);
|
||||
}
|
||||
|
||||
#include "mtpScheme.h"
|
||||
#include "mtproto/scheme_auto.h"
|
||||
|
||||
inline MTPbool MTP_bool(bool v) {
|
||||
return v ? MTP_boolTrue() : MTP_boolFalse();
|
||||
|
@ -977,16 +979,104 @@ inline bool mtpIsFalse(const MTPBool &v) {
|
|||
return !mtpIsTrue(v);
|
||||
}
|
||||
|
||||
enum { // client side flags
|
||||
MTPDmessage_flag_HAS_TEXT_LINKS = (1 << 31), // message has links for "shared links" indexing
|
||||
MTPDmessage_flag_IS_GROUP_MIGRATE = (1 << 30), // message is a group migrate (group -> supergroup) service message
|
||||
MTPDreplyKeyboardMarkup_flag_FORCE_REPLY = (1 << 30), // markup just wants a text reply
|
||||
MTPDreplyKeyboardMarkup_flag_ZERO = (1 << 31), // none (zero) markup
|
||||
MTPDstickerSet_flag_NOT_LOADED = (1 << 31), // sticker set is not yet loaded
|
||||
#define CHECK_MTP_SCHEME_AND_CLIENT_FLAGS_CONFLICT(Type) \
|
||||
|
||||
// we must validate that MTProto scheme flags don't intersect with client side flags
|
||||
// and define common bit operators which allow use Type_ClientFlag together with Type::Flag
|
||||
#define DEFINE_MTP_CLIENT_FLAGS(Type) \
|
||||
static_assert(static_cast<int32>(Type::Flag::MAX_FIELD) < static_cast<int32>(Type##_ClientFlag::MIN_FIELD), \
|
||||
"MTProto flags conflict with client side flags!"); \
|
||||
inline Type::Flags qFlags(Type##_ClientFlag v) { return Type::Flags(static_cast<int32>(v)); } \
|
||||
inline Type::Flags operator&(Type::Flags i, Type##_ClientFlag v) { return i & qFlags(v); } \
|
||||
inline Type::Flags operator&(Type::Flag i, Type##_ClientFlag v) { return qFlags(i) & v; } \
|
||||
inline Type::Flags operator&(Type##_ClientFlag i, Type##_ClientFlag v) { return qFlags(i) & v; } \
|
||||
inline Type::Flags operator&(Type##_ClientFlag i, Type::Flag v) { return qFlags(i) & v; } \
|
||||
inline Type::Flags &operator&=(Type::Flags &i, Type##_ClientFlag v) { return i &= qFlags(v); } \
|
||||
inline Type::Flags operator|(Type::Flags i, Type##_ClientFlag v) { return i | qFlags(v); } \
|
||||
inline Type::Flags operator|(Type::Flag i, Type##_ClientFlag v) { return qFlags(i) | v; } \
|
||||
inline Type::Flags operator|(Type##_ClientFlag i, Type##_ClientFlag v) { return qFlags(i) | v; } \
|
||||
inline Type::Flags operator|(Type##_ClientFlag i, Type::Flag v) { return qFlags(i) | v; } \
|
||||
inline Type::Flags &operator|=(Type::Flags &i, Type##_ClientFlag v) { return i |= qFlags(v); } \
|
||||
inline Type::Flags operator~(Type##_ClientFlag v) { return ~qFlags(v); }
|
||||
|
||||
// we use the same flags field for some additional client side flags
|
||||
enum class MTPDmessage_ClientFlag : int32 {
|
||||
// message has links for "shared links" indexing
|
||||
f_has_text_links = (1 << 30),
|
||||
|
||||
// message is a group migrate (group -> supergroup) service message
|
||||
f_is_group_migrate = (1 << 29),
|
||||
|
||||
// message needs initDimensions() + resize() + paint()
|
||||
f_pending_init_dimensions = (1 << 28),
|
||||
|
||||
// message needs resize() + paint()
|
||||
f_pending_resize = (1 << 27),
|
||||
|
||||
// message needs paint()
|
||||
f_pending_paint = (1 << 26),
|
||||
|
||||
// message is attached to previous one when displaying the history
|
||||
f_attach_to_previous = (1 << 25),
|
||||
|
||||
// update this when adding new client side flags
|
||||
MIN_FIELD = (1 << 25),
|
||||
};
|
||||
DEFINE_MTP_CLIENT_FLAGS(MTPDmessage)
|
||||
|
||||
enum class MTPDreplyKeyboardMarkup_ClientFlag : int32 {
|
||||
// none (zero) markup
|
||||
f_zero = (1 << 30),
|
||||
|
||||
// markup just wants a text reply
|
||||
f_force_reply = (1 << 29),
|
||||
|
||||
// update this when adding new client side flags
|
||||
MIN_FIELD = (1 << 29),
|
||||
};
|
||||
DEFINE_MTP_CLIENT_FLAGS(MTPDreplyKeyboardMarkup)
|
||||
|
||||
enum class MTPDstickerSet_ClientFlag : int32 {
|
||||
// old value for sticker set is not yet loaded flag
|
||||
f_not_loaded__old = (1 << 31),
|
||||
|
||||
// sticker set is not yet loaded
|
||||
f_not_loaded = (1 << 30),
|
||||
|
||||
// update this when adding new client side flags
|
||||
MIN_FIELD = (1 << 30),
|
||||
};
|
||||
DEFINE_MTP_CLIENT_FLAGS(MTPDstickerSet)
|
||||
|
||||
extern const MTPReplyMarkup MTPnullMarkup;
|
||||
extern const MTPVector<MTPMessageEntity> MTPnullEntities;
|
||||
extern const MTPMessageFwdHeader MTPnullFwdHeader;
|
||||
|
||||
QString stickerSetTitle(const MTPDstickerSet &s);
|
||||
|
||||
inline bool mtpRequestData::isSentContainer(const mtpRequest &request) { // "request-like" wrap for msgIds vector
|
||||
if (request->size() < 9) return false;
|
||||
return (!request->msDate && !(*request)[6]); // msDate = 0, seqNo = 0
|
||||
}
|
||||
inline bool mtpRequestData::isStateRequest(const mtpRequest &request) {
|
||||
if (request->size() < 9) return false;
|
||||
return (mtpTypeId((*request)[8]) == mtpc_msgs_state_req);
|
||||
}
|
||||
inline bool mtpRequestData::needAck(const mtpRequest &request) {
|
||||
if (request->size() < 9) return false;
|
||||
return mtpRequestData::needAckByType((*request)[8]);
|
||||
}
|
||||
inline bool mtpRequestData::needAckByType(mtpTypeId type) {
|
||||
switch (type) {
|
||||
case mtpc_msg_container:
|
||||
case mtpc_msgs_ack:
|
||||
case mtpc_http_wait:
|
||||
case mtpc_bad_msg_notification:
|
||||
case mtpc_msgs_all_info:
|
||||
case mtpc_msgs_state_info:
|
||||
case mtpc_msg_detailed_info:
|
||||
case mtpc_msg_new_detailed_info:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -19,44 +19,47 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "mtpDC.h"
|
||||
#include "mtp.h"
|
||||
|
||||
#include "mtproto/dcenter.h"
|
||||
|
||||
#include "mtproto/facade.h"
|
||||
#include "localstorage.h"
|
||||
|
||||
namespace {
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
MTProtoDCMap gDCs;
|
||||
namespace {
|
||||
DcenterMap gDCs;
|
||||
bool configLoadedOnce = false;
|
||||
bool mainDCChanged = false;
|
||||
int32 mainDC = 2;
|
||||
int32 _mainDC = 2;
|
||||
int32 userId = 0;
|
||||
|
||||
typedef QMap<int32, mtpAuthKeyPtr> _KeysMapForWrite;
|
||||
typedef QMap<int32, AuthKeyPtr> _KeysMapForWrite;
|
||||
_KeysMapForWrite _keysMapForWrite;
|
||||
QMutex _keysMapForWriteMutex;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
int32 mtpAuthed() {
|
||||
int32 authed() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
void mtpAuthed(int32 uid) {
|
||||
void authed(int32 uid) {
|
||||
if (userId != uid) {
|
||||
userId = uid;
|
||||
}
|
||||
}
|
||||
|
||||
MTProtoDCMap &mtpDCMap() {
|
||||
DcenterMap &DCMap() {
|
||||
return gDCs;
|
||||
}
|
||||
|
||||
bool mtpNeedConfig() {
|
||||
bool configNeeded() {
|
||||
return !configLoadedOnce;
|
||||
}
|
||||
|
||||
int32 mtpMainDC() {
|
||||
return mainDC;
|
||||
int32 mainDC() {
|
||||
return _mainDC;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -73,7 +76,7 @@ namespace {
|
|||
}
|
||||
}
|
||||
|
||||
void mtpLogoutOtherDCs() {
|
||||
void logoutOtherDCs() {
|
||||
QList<int32> dcs;
|
||||
{
|
||||
QMutexLocker lock(&_keysMapForWriteMutex);
|
||||
|
@ -81,20 +84,20 @@ void mtpLogoutOtherDCs() {
|
|||
}
|
||||
for (int32 i = 0, cnt = dcs.size(); i != cnt; ++i) {
|
||||
if (dcs[i] != MTP::maindc()) {
|
||||
logoutGuestMap.insert(MTP::lgt + dcs[i], MTP::send(MTPauth_LogOut(), rpcDone(&logoutDone), rpcFail(&logoutDone), MTP::lgt + dcs[i]));
|
||||
logoutGuestMap.insert(MTP::lgtDcId(dcs[i]), MTP::send(MTPauth_LogOut(), rpcDone(&logoutDone), rpcFail(&logoutDone), MTP::lgtDcId(dcs[i])));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mtpSetDC(int32 dc, bool firstOnly) {
|
||||
void setDC(int32 dc, bool firstOnly) {
|
||||
if (!dc || (firstOnly && mainDCChanged)) return;
|
||||
mainDCChanged = true;
|
||||
if (dc != mainDC) {
|
||||
mainDC = dc;
|
||||
if (dc != _mainDC) {
|
||||
_mainDC = dc;
|
||||
}
|
||||
}
|
||||
|
||||
MTProtoDC::MTProtoDC(int32 id, const mtpAuthKeyPtr &key) : _id(id), _key(key), _connectionInited(false) {
|
||||
Dcenter::Dcenter(int32 id, const AuthKeyPtr &key) : _id(id), _key(key), _connectionInited(false) {
|
||||
connect(this, SIGNAL(authKeyCreated()), this, SLOT(authKeyWrite()), Qt::QueuedConnection);
|
||||
|
||||
QMutexLocker lock(&_keysMapForWriteMutex);
|
||||
|
@ -105,14 +108,14 @@ MTProtoDC::MTProtoDC(int32 id, const mtpAuthKeyPtr &key) : _id(id), _key(key), _
|
|||
}
|
||||
}
|
||||
|
||||
void MTProtoDC::authKeyWrite() {
|
||||
void Dcenter::authKeyWrite() {
|
||||
DEBUG_LOG(("AuthKey Info: MTProtoDC::authKeyWrite() slot, dc %1").arg(_id));
|
||||
if (_key) {
|
||||
Local::writeMtpData();
|
||||
}
|
||||
}
|
||||
|
||||
void MTProtoDC::setKey(const mtpAuthKeyPtr &key) {
|
||||
void Dcenter::setKey(const AuthKeyPtr &key) {
|
||||
DEBUG_LOG(("AuthKey Info: MTProtoDC::setKey(%1), emitting authKeyCreated, dc %2").arg(key ? key->keyId() : 0).arg(_id));
|
||||
_key = key;
|
||||
_connectionInited = false;
|
||||
|
@ -126,23 +129,23 @@ void MTProtoDC::setKey(const mtpAuthKeyPtr &key) {
|
|||
}
|
||||
}
|
||||
|
||||
QReadWriteLock *MTProtoDC::keyMutex() const {
|
||||
QReadWriteLock *Dcenter::keyMutex() const {
|
||||
return &keyLock;
|
||||
}
|
||||
|
||||
const mtpAuthKeyPtr &MTProtoDC::getKey() const {
|
||||
const AuthKeyPtr &Dcenter::getKey() const {
|
||||
return _key;
|
||||
}
|
||||
|
||||
void MTProtoDC::destroyKey() {
|
||||
setKey(mtpAuthKeyPtr());
|
||||
void Dcenter::destroyKey() {
|
||||
setKey(AuthKeyPtr());
|
||||
|
||||
QMutexLocker lock(&_keysMapForWriteMutex);
|
||||
_keysMapForWrite.remove(_id);
|
||||
}
|
||||
|
||||
namespace {
|
||||
MTProtoConfigLoader *configLoader = 0;
|
||||
ConfigLoader *_configLoader = nullptr;
|
||||
bool loadingConfig = false;
|
||||
void configLoaded(const MTPConfig &result) {
|
||||
loadingConfig = false;
|
||||
|
@ -151,7 +154,7 @@ namespace {
|
|||
|
||||
DEBUG_LOG(("MTP Info: got config, chat_size_max: %1, date: %2, test_mode: %3, this_dc: %4, dc_options.length: %5").arg(data.vchat_size_max.v).arg(data.vdate.v).arg(mtpIsTrue(data.vtest_mode)).arg(data.vthis_dc.v).arg(data.vdc_options.c_vector().v.size()));
|
||||
|
||||
mtpUpdateDcOptions(data.vdc_options.c_vector().v);
|
||||
updateDcOptions(data.vdc_options.c_vector().v);
|
||||
|
||||
Global::SetChatSizeMax(data.vchat_size_max.v);
|
||||
Global::SetMegagroupSizeMax(data.vmegagroup_size_max.v);
|
||||
|
@ -171,7 +174,7 @@ namespace {
|
|||
configLoadedOnce = true;
|
||||
Local::writeSettings();
|
||||
|
||||
mtpConfigLoader()->done();
|
||||
configLoader()->done();
|
||||
}
|
||||
bool configFailed(const RPCError &error) {
|
||||
if (mtpIsFlood(error)) return false;
|
||||
|
@ -182,31 +185,31 @@ namespace {
|
|||
}
|
||||
};
|
||||
|
||||
void mtpUpdateDcOptions(const QVector<MTPDcOption> &options) {
|
||||
void updateDcOptions(const QVector<MTPDcOption> &options) {
|
||||
QSet<int32> already, restart;
|
||||
{
|
||||
mtpDcOptions opts;
|
||||
MTP::DcOptions opts;
|
||||
{
|
||||
QReadLocker lock(mtpDcOptionsMutex());
|
||||
opts = cDcOptions();
|
||||
QReadLocker lock(dcOptionsMutex());
|
||||
opts = Global::DcOptions();
|
||||
}
|
||||
for (QVector<MTPDcOption>::const_iterator i = options.cbegin(), e = options.cend(); i != e; ++i) {
|
||||
const MTPDdcOption &optData(i->c_dcOption());
|
||||
int32 id = optData.vid.v, idWithShift = id + (optData.vflags.v * _mtp_internal::dcShift);
|
||||
int32 id = optData.vid.v, idWithShift = MTP::shiftDcId(id, optData.vflags.v);
|
||||
if (already.constFind(idWithShift) == already.cend()) {
|
||||
already.insert(idWithShift);
|
||||
mtpDcOptions::const_iterator a = opts.constFind(idWithShift);
|
||||
auto a = opts.constFind(idWithShift);
|
||||
if (a != opts.cend()) {
|
||||
if (a.value().ip != optData.vip_address.c_string().v || a.value().port != optData.vport.v) {
|
||||
restart.insert(id);
|
||||
}
|
||||
}
|
||||
opts.insert(idWithShift, mtpDcOption(id, optData.vflags.v, optData.vip_address.c_string().v, optData.vport.v));
|
||||
opts.insert(idWithShift, MTP::DcOption(id, optData.vflags.v, optData.vip_address.c_string().v, optData.vport.v));
|
||||
}
|
||||
}
|
||||
{
|
||||
QWriteLocker lock(mtpDcOptionsMutex());
|
||||
cSetDcOptions(opts);
|
||||
QWriteLocker lock(dcOptionsMutex());
|
||||
Global::SetDcOptions(opts);
|
||||
}
|
||||
}
|
||||
for (QSet<int32>::const_iterator i = restart.cbegin(), e = restart.cend(); i != e; ++i) {
|
||||
|
@ -218,15 +221,15 @@ namespace {
|
|||
QReadWriteLock _dcOptionsMutex;
|
||||
}
|
||||
|
||||
QReadWriteLock *mtpDcOptionsMutex() {
|
||||
QReadWriteLock *dcOptionsMutex() {
|
||||
return &_dcOptionsMutex;
|
||||
}
|
||||
|
||||
MTProtoConfigLoader::MTProtoConfigLoader() : _enumCurrent(0), _enumRequest(0) {
|
||||
ConfigLoader::ConfigLoader() : _enumCurrent(0), _enumRequest(0) {
|
||||
connect(&_enumDCTimer, SIGNAL(timeout()), this, SLOT(enumDC()));
|
||||
}
|
||||
|
||||
void MTProtoConfigLoader::load() {
|
||||
void ConfigLoader::load() {
|
||||
if (loadingConfig) return;
|
||||
loadingConfig = true;
|
||||
|
||||
|
@ -235,68 +238,71 @@ void MTProtoConfigLoader::load() {
|
|||
_enumDCTimer.start(MTPEnumDCTimeout);
|
||||
}
|
||||
|
||||
void MTProtoConfigLoader::done() {
|
||||
void ConfigLoader::done() {
|
||||
_enumDCTimer.stop();
|
||||
if (_enumRequest) {
|
||||
MTP::cancel(_enumRequest);
|
||||
_enumRequest = 0;
|
||||
}
|
||||
if (_enumCurrent) {
|
||||
MTP::killSession(MTP::cfg + _enumCurrent);
|
||||
MTP::killSession(MTP::cfgDcId(_enumCurrent));
|
||||
_enumCurrent = 0;
|
||||
}
|
||||
emit loaded();
|
||||
}
|
||||
|
||||
void MTProtoConfigLoader::enumDC() {
|
||||
void ConfigLoader::enumDC() {
|
||||
if (!loadingConfig) return;
|
||||
|
||||
if (_enumRequest) MTP::cancel(_enumRequest);
|
||||
|
||||
if (!_enumCurrent) {
|
||||
_enumCurrent = mainDC;
|
||||
_enumCurrent = _mainDC;
|
||||
} else {
|
||||
MTP::killSession(MTP::cfg + _enumCurrent);
|
||||
MTP::killSession(MTP::cfgDcId(_enumCurrent));
|
||||
}
|
||||
OrderedSet<int32> dcs;
|
||||
{
|
||||
QReadLocker lock(mtpDcOptionsMutex());
|
||||
const mtpDcOptions &options(cDcOptions());
|
||||
for (mtpDcOptions::const_iterator i = options.cbegin(), e = options.cend(); i != e; ++i) {
|
||||
dcs.insert(i.key() % _mtp_internal::dcShift);
|
||||
QReadLocker lock(dcOptionsMutex());
|
||||
const MTP::DcOptions &options(Global::DcOptions());
|
||||
for (auto i = options.cbegin(), e = options.cend(); i != e; ++i) {
|
||||
dcs.insert(MTP::bareDcId(i.key()));
|
||||
}
|
||||
}
|
||||
OrderedSet<int32>::const_iterator i = dcs.constFind(_enumCurrent);
|
||||
auto i = dcs.constFind(_enumCurrent);
|
||||
if (i == dcs.cend() || (++i) == dcs.cend()) {
|
||||
_enumCurrent = dcs.cbegin().key();
|
||||
_enumCurrent = *dcs.cbegin();
|
||||
} else {
|
||||
_enumCurrent = i.key();
|
||||
_enumCurrent = *i;
|
||||
}
|
||||
_enumRequest = MTP::send(MTPhelp_GetConfig(), rpcDone(configLoaded), rpcFail(configFailed), MTP::cfg + _enumCurrent);
|
||||
_enumRequest = MTP::send(MTPhelp_GetConfig(), rpcDone(configLoaded), rpcFail(configFailed), MTP::cfgDcId(_enumCurrent));
|
||||
|
||||
_enumDCTimer.start(MTPEnumDCTimeout);
|
||||
}
|
||||
|
||||
MTProtoConfigLoader *mtpConfigLoader() {
|
||||
if (!configLoader) configLoader = new MTProtoConfigLoader();
|
||||
return configLoader;
|
||||
ConfigLoader *configLoader() {
|
||||
if (!_configLoader) _configLoader = new ConfigLoader();
|
||||
return _configLoader;
|
||||
}
|
||||
|
||||
void mtpDestroyConfigLoader() {
|
||||
delete configLoader;
|
||||
configLoader = 0;
|
||||
void destroyConfigLoader() {
|
||||
delete _configLoader;
|
||||
_configLoader = nullptr;
|
||||
}
|
||||
|
||||
mtpKeysMap mtpGetKeys() {
|
||||
mtpKeysMap result;
|
||||
AuthKeysMap getAuthKeys() {
|
||||
AuthKeysMap result;
|
||||
QMutexLocker lock(&_keysMapForWriteMutex);
|
||||
for (_KeysMapForWrite::const_iterator i = _keysMapForWrite.cbegin(), e = _keysMapForWrite.cend(); i != e; ++i) {
|
||||
result.push_back(i.value());
|
||||
for_const (const AuthKeyPtr &key, _keysMapForWrite) {
|
||||
result.push_back(key);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void mtpSetKey(int32 dcId, mtpAuthKeyPtr key) {
|
||||
MTProtoDCPtr dc(new MTProtoDC(dcId, key));
|
||||
void setAuthKey(int32 dcId, AuthKeyPtr key) {
|
||||
DcenterPtr dc(new Dcenter(dcId, key));
|
||||
gDCs.insert(dcId, dc);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
|
@ -20,16 +20,19 @@ Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
class MTProtoDC : public QObject {
|
||||
namespace MTP {
|
||||
namespace internal {
|
||||
|
||||
class Dcenter : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MTProtoDC(int32 id, const mtpAuthKeyPtr &key);
|
||||
Dcenter(int32 id, const AuthKeyPtr &key);
|
||||
|
||||
QReadWriteLock *keyMutex() const;
|
||||
const mtpAuthKeyPtr &getKey() const;
|
||||
void setKey(const mtpAuthKeyPtr &key);
|
||||
const AuthKeyPtr &getKey() const;
|
||||
void setKey(const AuthKeyPtr &key);
|
||||
void destroyKey();
|
||||
|
||||
bool connectionInited() const {
|
||||
|
@ -56,19 +59,19 @@ private:
|
|||
mutable QReadWriteLock keyLock;
|
||||
mutable QMutex initLock;
|
||||
int32 _id;
|
||||
mtpAuthKeyPtr _key;
|
||||
AuthKeyPtr _key;
|
||||
bool _connectionInited;
|
||||
};
|
||||
|
||||
typedef QSharedPointer<MTProtoDC> MTProtoDCPtr;
|
||||
typedef QMap<uint32, MTProtoDCPtr> MTProtoDCMap;
|
||||
typedef QSharedPointer<Dcenter> DcenterPtr;
|
||||
typedef QMap<uint32, DcenterPtr> DcenterMap;
|
||||
|
||||
class MTProtoConfigLoader : public QObject {
|
||||
class ConfigLoader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MTProtoConfigLoader();
|
||||
ConfigLoader();
|
||||
void load();
|
||||
void done();
|
||||
|
||||
|
@ -88,21 +91,23 @@ private:
|
|||
|
||||
};
|
||||
|
||||
MTProtoConfigLoader *mtpConfigLoader();
|
||||
void mtpDestroyConfigLoader();
|
||||
ConfigLoader *configLoader();
|
||||
void destroyConfigLoader();
|
||||
|
||||
MTProtoDCMap &mtpDCMap();
|
||||
bool mtpNeedConfig();
|
||||
int32 mtpMainDC();
|
||||
void mtpLogoutOtherDCs();
|
||||
void mtpSetDC(int32 dc, bool firstOnly = false);
|
||||
uint32 mtpMaxChatSize();
|
||||
DcenterMap &DCMap();
|
||||
bool configNeeded();
|
||||
int32 mainDC();
|
||||
void logoutOtherDCs();
|
||||
void setDC(int32 dc, bool firstOnly = false);
|
||||
|
||||
int32 mtpAuthed();
|
||||
void mtpAuthed(int32 uid);
|
||||
int32 authed();
|
||||
void authed(int32 uid);
|
||||
|
||||
mtpKeysMap mtpGetKeys();
|
||||
void mtpSetKey(int32 dc, mtpAuthKeyPtr key);
|
||||
AuthKeysMap getAuthKeys();
|
||||
void setAuthKey(int32 dc, AuthKeyPtr key);
|
||||
|
||||
void mtpUpdateDcOptions(const QVector<MTPDcOption> &options);
|
||||
QReadWriteLock *mtpDcOptionsMutex();
|
||||
void updateDcOptions(const QVector<MTPDcOption> &options);
|
||||
QReadWriteLock *dcOptionsMutex();
|
||||
|
||||
} // namespace internal
|
||||
} // namespace MTP
|
907
Telegram/SourceFiles/mtproto/facade.cpp
Normal file
907
Telegram/SourceFiles/mtproto/facade.cpp
Normal file
|
@ -0,0 +1,907 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "mtproto/facade.h"
|
||||
|
||||
#include "localstorage.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
namespace {
|
||||
|
||||
typedef QMap<int32, internal::Session*> Sessions;
|
||||
Sessions sessions;
|
||||
internal::Session *mainSession;
|
||||
|
||||
typedef QMap<mtpRequestId, int32> RequestsByDC; // holds dcWithShift for request to this dc or -dc for request to main dc
|
||||
RequestsByDC requestsByDC;
|
||||
QMutex requestByDCLock;
|
||||
|
||||
typedef QMap<mtpRequestId, int32> AuthExportRequests; // holds target dcWithShift for auth export request
|
||||
AuthExportRequests authExportRequests;
|
||||
|
||||
bool _started = false;
|
||||
|
||||
uint32 layer;
|
||||
|
||||
typedef QMap<mtpRequestId, RPCResponseHandler> ParserMap;
|
||||
ParserMap parserMap;
|
||||
QMutex parserMapLock;
|
||||
|
||||
typedef QMap<mtpRequestId, mtpRequest> RequestMap;
|
||||
RequestMap requestMap;
|
||||
QReadWriteLock requestMapLock;
|
||||
|
||||
typedef QPair<mtpRequestId, uint64> DelayedRequest;
|
||||
typedef QList<DelayedRequest> DelayedRequestsList;
|
||||
DelayedRequestsList delayedRequests;
|
||||
|
||||
typedef QMap<mtpRequestId, int32> RequestsDelays;
|
||||
RequestsDelays requestsDelays;
|
||||
|
||||
typedef QSet<mtpRequestId> BadGuestDCRequests;
|
||||
BadGuestDCRequests badGuestDCRequests;
|
||||
|
||||
typedef QVector<mtpRequestId> DCAuthWaiters;
|
||||
typedef QMap<int32, DCAuthWaiters> AuthWaiters; // holds request ids waiting for auth import to specific dc
|
||||
AuthWaiters authWaiters;
|
||||
|
||||
typedef OrderedSet<internal::Connection*> MTPQuittingConnections;
|
||||
MTPQuittingConnections quittingConnections;
|
||||
|
||||
QMutex toClearLock;
|
||||
RPCCallbackClears toClear;
|
||||
|
||||
RPCResponseHandler globalHandler;
|
||||
MTPStateChangedHandler stateChangedHandler = 0;
|
||||
MTPSessionResetHandler sessionResetHandler = 0;
|
||||
internal::GlobalSlotCarrier *_globalSlotCarrier = 0;
|
||||
|
||||
void importDone(const MTPauth_Authorization &result, mtpRequestId req) {
|
||||
QMutexLocker locker1(&requestByDCLock);
|
||||
|
||||
RequestsByDC::iterator i = requestsByDC.find(req);
|
||||
if (i == requestsByDC.end()) {
|
||||
LOG(("MTP Error: auth import request not found in requestsByDC, requestId: %1").arg(req));
|
||||
RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find import request in requestsByDC, request %1").arg(req)));
|
||||
if (globalHandler.onFail && authedId()) (*globalHandler.onFail)(req, error); // auth failed in main dc
|
||||
return;
|
||||
}
|
||||
DcId newdc = bareDcId(i.value());
|
||||
|
||||
DEBUG_LOG(("MTP Info: auth import to dc %1 succeeded").arg(newdc));
|
||||
|
||||
DCAuthWaiters &waiters(authWaiters[newdc]);
|
||||
if (waiters.size()) {
|
||||
QReadLocker locker(&requestMapLock);
|
||||
for (DCAuthWaiters::iterator i = waiters.begin(), e = waiters.end(); i != e; ++i) {
|
||||
mtpRequestId requestId = *i;
|
||||
RequestMap::const_iterator j = requestMap.constFind(requestId);
|
||||
if (j == requestMap.cend()) {
|
||||
LOG(("MTP Error: could not find request %1 for resending").arg(requestId));
|
||||
continue;
|
||||
}
|
||||
ShiftedDcId dcWithShift = newdc;
|
||||
{
|
||||
RequestsByDC::iterator k = requestsByDC.find(requestId);
|
||||
if (k == requestsByDC.cend()) {
|
||||
LOG(("MTP Error: could not find request %1 by dc for resending").arg(requestId));
|
||||
continue;
|
||||
}
|
||||
if (k.value() < 0) {
|
||||
setdc(newdc);
|
||||
k.value() = -newdc;
|
||||
} else {
|
||||
dcWithShift = shiftDcId(newdc, getDcIdShift(k.value()));
|
||||
k.value() = dcWithShift;
|
||||
}
|
||||
DEBUG_LOG(("MTP Info: resending request %1 to dc %2 after import auth").arg(requestId).arg(k.value()));
|
||||
}
|
||||
if (internal::Session *session = internal::getSession(dcWithShift)) {
|
||||
session->sendPrepared(j.value());
|
||||
}
|
||||
}
|
||||
waiters.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool importFail(const RPCError &error, mtpRequestId req) {
|
||||
if (mtpIsFlood(error)) return false;
|
||||
|
||||
if (globalHandler.onFail && authedId()) (*globalHandler.onFail)(req, error); // auth import failed
|
||||
return true;
|
||||
}
|
||||
|
||||
void exportDone(const MTPauth_ExportedAuthorization &result, mtpRequestId req) {
|
||||
AuthExportRequests::const_iterator i = authExportRequests.constFind(req);
|
||||
if (i == authExportRequests.cend()) {
|
||||
LOG(("MTP Error: auth export request target dcWithShift not found, requestId: %1").arg(req));
|
||||
RPCError error(internal::rpcClientError("AUTH_IMPORT_FAIL", QString("did not find target dcWithShift, request %1").arg(req)));
|
||||
if (globalHandler.onFail && authedId()) (*globalHandler.onFail)(req, error); // auth failed in main dc
|
||||
return;
|
||||
}
|
||||
|
||||
const MTPDauth_exportedAuthorization &data(result.c_auth_exportedAuthorization());
|
||||
send(MTPauth_ImportAuthorization(data.vid, data.vbytes), rpcDone(importDone), rpcFail(importFail), i.value());
|
||||
authExportRequests.remove(req);
|
||||
}
|
||||
|
||||
bool exportFail(const RPCError &error, mtpRequestId req) {
|
||||
if (mtpIsFlood(error)) return false;
|
||||
|
||||
AuthExportRequests::const_iterator i = authExportRequests.constFind(req);
|
||||
if (i != authExportRequests.cend()) {
|
||||
authWaiters[bareDcId(i.value())].clear();
|
||||
}
|
||||
if (globalHandler.onFail && authedId()) (*globalHandler.onFail)(req, error); // auth failed in main dc
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onErrorDefault(mtpRequestId requestId, const RPCError &error) {
|
||||
const QString &err(error.type());
|
||||
int32 code = error.code();
|
||||
if (!mtpIsFlood(error) && err != qsl("AUTH_KEY_UNREGISTERED")) {
|
||||
int breakpoint = 0;
|
||||
}
|
||||
bool badGuestDC = (code == 400) && (err == qsl("FILE_ID_INVALID"));
|
||||
QRegularExpressionMatch m;
|
||||
if ((m = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$").match(err)).hasMatch()) {
|
||||
if (!requestId) return false;
|
||||
|
||||
ShiftedDcId dcWithShift = 0, newdcWithShift = m.captured(2).toInt();
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i == requestsByDC.end()) {
|
||||
LOG(("MTP Error: could not find request %1 for migrating to %2").arg(requestId).arg(newdcWithShift));
|
||||
} else {
|
||||
dcWithShift = i.value();
|
||||
}
|
||||
}
|
||||
if (!dcWithShift || !newdcWithShift) return false;
|
||||
|
||||
DEBUG_LOG(("MTP Info: changing request %1 from dcWithShift%2 to dc%3").arg(requestId).arg(dcWithShift).arg(newdcWithShift));
|
||||
if (dcWithShift < 0) { // newdc shift = 0
|
||||
if (false && authedId() && !authExportRequests.contains(requestId)) { // migrate not supported at this moment
|
||||
DEBUG_LOG(("MTP Info: importing auth to dc %1").arg(newdcWithShift));
|
||||
DCAuthWaiters &waiters(authWaiters[newdcWithShift]);
|
||||
if (!waiters.size()) {
|
||||
authExportRequests.insert(send(MTPauth_ExportAuthorization(MTP_int(newdcWithShift)), rpcDone(exportDone), rpcFail(exportFail)), newdcWithShift);
|
||||
}
|
||||
waiters.push_back(requestId);
|
||||
return true;
|
||||
} else {
|
||||
MTP::setdc(newdcWithShift);
|
||||
}
|
||||
} else {
|
||||
newdcWithShift = shiftDcId(newdcWithShift, getDcIdShift(dcWithShift));
|
||||
}
|
||||
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator i = requestMap.constFind(requestId);
|
||||
if (i == requestMap.cend()) {
|
||||
LOG(("MTP Error: could not find request %1").arg(requestId));
|
||||
return false;
|
||||
}
|
||||
req = i.value();
|
||||
}
|
||||
if (internal::Session *session = internal::getSession(newdcWithShift)) {
|
||||
internal::registerRequest(requestId, (dcWithShift < 0) ? -newdcWithShift : newdcWithShift);
|
||||
session->sendPrepared(req);
|
||||
}
|
||||
return true;
|
||||
} else if (code < 0 || code >= 500 || (m = QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(err)).hasMatch()) {
|
||||
if (!requestId) return false;
|
||||
|
||||
int32 secs = 1;
|
||||
if (code < 0 || code >= 500) {
|
||||
RequestsDelays::iterator i = requestsDelays.find(requestId);
|
||||
if (i != requestsDelays.cend()) {
|
||||
secs = (i.value() > 60) ? i.value() : (i.value() *= 2);
|
||||
} else {
|
||||
requestsDelays.insert(requestId, secs);
|
||||
}
|
||||
} else {
|
||||
secs = m.captured(1).toInt();
|
||||
if (secs >= 60) return false;
|
||||
}
|
||||
uint64 sendAt = getms(true) + secs * 1000 + 10;
|
||||
DelayedRequestsList::iterator i = delayedRequests.begin(), e = delayedRequests.end();
|
||||
for (; i != e; ++i) {
|
||||
if (i->first == requestId) return true;
|
||||
if (i->second > sendAt) break;
|
||||
}
|
||||
delayedRequests.insert(i, DelayedRequest(requestId, sendAt));
|
||||
|
||||
if (_globalSlotCarrier) _globalSlotCarrier->checkDelayed();
|
||||
|
||||
return true;
|
||||
} else if (code == 401 || (badGuestDC && badGuestDCRequests.constFind(requestId) == badGuestDCRequests.cend())) {
|
||||
int32 dcWithShift = 0;
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i != requestsByDC.end()) {
|
||||
dcWithShift = i.value();
|
||||
} else {
|
||||
LOG(("MTP Error: unauthorized request without dc info, requestId %1").arg(requestId));
|
||||
}
|
||||
}
|
||||
int32 newdc = bareDcId(qAbs(dcWithShift));
|
||||
if (!newdc || newdc == internal::mainDC() || !authedId()) {
|
||||
if (!badGuestDC && globalHandler.onFail) (*globalHandler.onFail)(requestId, error); // auth failed in main dc
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("MTP Info: importing auth to dcWithShift %1").arg(dcWithShift));
|
||||
DCAuthWaiters &waiters(authWaiters[newdc]);
|
||||
if (!waiters.size()) {
|
||||
authExportRequests.insert(send(MTPauth_ExportAuthorization(MTP_int(newdc)), rpcDone(exportDone), rpcFail(exportFail)), abs(dcWithShift));
|
||||
}
|
||||
waiters.push_back(requestId);
|
||||
if (badGuestDC) badGuestDCRequests.insert(requestId);
|
||||
return true;
|
||||
} else if (err == qsl("CONNECTION_NOT_INITED") || err == qsl("CONNECTION_LAYER_INVALID")) {
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator i = requestMap.constFind(requestId);
|
||||
if (i == requestMap.cend()) {
|
||||
LOG(("MTP Error: could not find request %1").arg(requestId));
|
||||
return false;
|
||||
}
|
||||
req = i.value();
|
||||
}
|
||||
int32 dcWithShift = 0;
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i == requestsByDC.end()) {
|
||||
LOG(("MTP Error: could not find request %1 for resending with init connection").arg(requestId));
|
||||
} else {
|
||||
dcWithShift = i.value();
|
||||
}
|
||||
}
|
||||
if (!dcWithShift) return false;
|
||||
|
||||
if (internal::Session *session = internal::getSession(qAbs(dcWithShift))) {
|
||||
req->needsLayer = true;
|
||||
session->sendPrepared(req);
|
||||
}
|
||||
return true;
|
||||
} else if (err == qsl("MSG_WAIT_FAILED")) {
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator i = requestMap.constFind(requestId);
|
||||
if (i == requestMap.cend()) {
|
||||
LOG(("MTP Error: could not find request %1").arg(requestId));
|
||||
return false;
|
||||
}
|
||||
req = i.value();
|
||||
}
|
||||
if (!req->after) {
|
||||
LOG(("MTP Error: wait failed for not dependent request %1").arg(requestId));
|
||||
return false;
|
||||
}
|
||||
int32 dcWithShift = 0;
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId), j = requestsByDC.find(req->after->requestId);
|
||||
if (i == requestsByDC.end()) {
|
||||
LOG(("MTP Error: could not find request %1 by dc").arg(requestId));
|
||||
} else if (j == requestsByDC.end()) {
|
||||
LOG(("MTP Error: could not find dependent request %1 by dc").arg(req->after->requestId));
|
||||
} else {
|
||||
dcWithShift = i.value();
|
||||
if (i.value() != j.value()) {
|
||||
req->after = mtpRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!dcWithShift) return false;
|
||||
|
||||
if (!req->after) {
|
||||
if (internal::Session *session = internal::getSession(qAbs(dcWithShift))) {
|
||||
req->needsLayer = true;
|
||||
session->sendPrepared(req);
|
||||
}
|
||||
} else {
|
||||
int32 newdc = bareDcId(qAbs(dcWithShift));
|
||||
DCAuthWaiters &waiters(authWaiters[newdc]);
|
||||
if (waiters.indexOf(req->after->requestId) >= 0) {
|
||||
if (waiters.indexOf(requestId) < 0) {
|
||||
waiters.push_back(requestId);
|
||||
}
|
||||
if (badGuestDCRequests.constFind(req->after->requestId) != badGuestDCRequests.cend()) {
|
||||
if (badGuestDCRequests.constFind(requestId) == badGuestDCRequests.cend()) {
|
||||
badGuestDCRequests.insert(requestId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint64 at = 0;
|
||||
DelayedRequestsList::iterator i = delayedRequests.begin(), e = delayedRequests.end();
|
||||
for (; i != e; ++i) {
|
||||
if (i->first == requestId) return true;
|
||||
if (i->first == req->after->requestId) break;
|
||||
}
|
||||
if (i != e) {
|
||||
delayedRequests.insert(i, DelayedRequest(requestId, i->second));
|
||||
}
|
||||
|
||||
if (_globalSlotCarrier) _globalSlotCarrier->checkDelayed();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (badGuestDC) badGuestDCRequests.remove(requestId);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _paused = false;
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace internal {
|
||||
|
||||
Session *getSession(ShiftedDcId shiftedDcId) {
|
||||
if (!_started) return nullptr;
|
||||
if (!shiftedDcId) return mainSession;
|
||||
if (!bareDcId(shiftedDcId)) {
|
||||
shiftedDcId += bareDcId(mainSession->getDcWithShift());
|
||||
}
|
||||
|
||||
Sessions::const_iterator i = sessions.constFind(shiftedDcId);
|
||||
if (i == sessions.cend()) {
|
||||
i = sessions.insert(shiftedDcId, new Session(shiftedDcId));
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
bool paused() {
|
||||
return _paused;
|
||||
}
|
||||
|
||||
void registerRequest(mtpRequestId requestId, int32 dcWithShift) {
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
requestsByDC.insert(requestId, dcWithShift);
|
||||
}
|
||||
internal::performDelayedClear(); // need to do it somewhere...
|
||||
}
|
||||
|
||||
void unregisterRequest(mtpRequestId requestId) {
|
||||
requestsDelays.remove(requestId);
|
||||
|
||||
{
|
||||
QWriteLocker locker(&requestMapLock);
|
||||
requestMap.remove(requestId);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
requestsByDC.remove(requestId);
|
||||
}
|
||||
|
||||
mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser) {
|
||||
mtpRequestId res = reqid();
|
||||
request->requestId = res;
|
||||
if (parser.onDone || parser.onFail) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
parserMap.insert(res, parser);
|
||||
}
|
||||
{
|
||||
QWriteLocker locker(&requestMapLock);
|
||||
requestMap.insert(res, request);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
mtpRequest getRequest(mtpRequestId reqId) {
|
||||
static mtpRequest zero;
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator i = requestMap.constFind(reqId);
|
||||
req = (i == requestMap.cend()) ? zero : i.value();
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest) {
|
||||
mtpMsgId afterId(*(mtpMsgId*)(from->after->data() + 4));
|
||||
mtpRequestMap::const_iterator i = afterId ? haveSent.constFind(afterId) : haveSent.cend();
|
||||
int32 size = to->size(), lenInInts = (from.innerLength() >> 2), headlen = 4, fulllen = headlen + lenInInts;
|
||||
if (i == haveSent.constEnd()) { // no invoke after or such msg was not sent or was completed recently
|
||||
to->resize(size + fulllen + skipBeforeRequest);
|
||||
if (skipBeforeRequest) {
|
||||
memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime));
|
||||
memcpy(to->data() + size + headlen + skipBeforeRequest, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime));
|
||||
} else {
|
||||
memcpy(to->data() + size, from->constData() + 4, fulllen * sizeof(mtpPrime));
|
||||
}
|
||||
} else {
|
||||
to->resize(size + fulllen + skipBeforeRequest + 3);
|
||||
memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime));
|
||||
(*to)[size + 3] += 3 * sizeof(mtpPrime);
|
||||
*((mtpTypeId*)&((*to)[size + headlen + skipBeforeRequest])) = mtpc_invokeAfterMsg;
|
||||
memcpy(to->data() + size + headlen + skipBeforeRequest + 1, &afterId, 2 * sizeof(mtpPrime));
|
||||
memcpy(to->data() + size + headlen + skipBeforeRequest + 3, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime));
|
||||
if (size + 3 != 7) (*to)[7] += 3 * sizeof(mtpPrime);
|
||||
}
|
||||
}
|
||||
|
||||
void clearCallbacks(mtpRequestId requestId, int32 errorCode) {
|
||||
RPCResponseHandler h;
|
||||
bool found = false;
|
||||
{
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
ParserMap::iterator i = parserMap.find(requestId);
|
||||
if (i != parserMap.end()) {
|
||||
h = i.value();
|
||||
found = true;
|
||||
|
||||
parserMap.erase(i);
|
||||
}
|
||||
}
|
||||
if (errorCode && found) {
|
||||
rpcErrorOccured(requestId, h, rpcClientError("CLEAR_CALLBACK", QString("did not handle request %1, error code %2").arg(requestId).arg(errorCode)));
|
||||
}
|
||||
}
|
||||
|
||||
void clearCallbacksDelayed(const RPCCallbackClears &requestIds) {
|
||||
uint32 idsCount = requestIds.size();
|
||||
if (!idsCount) return;
|
||||
|
||||
if (cDebug()) {
|
||||
QString idsStr = QString("%1").arg(requestIds[0].requestId);
|
||||
for (uint32 i = 1; i < idsCount; ++i) {
|
||||
idsStr += QString(", %1").arg(requestIds[i].requestId);
|
||||
}
|
||||
DEBUG_LOG(("RPC Info: clear callbacks delayed, msgIds: %1").arg(idsStr));
|
||||
}
|
||||
|
||||
QMutexLocker lock(&toClearLock);
|
||||
uint32 toClearNow = toClear.size();
|
||||
if (toClearNow) {
|
||||
toClear.resize(toClearNow + idsCount);
|
||||
memcpy(toClear.data() + toClearNow, requestIds.constData(), idsCount * sizeof(RPCCallbackClear));
|
||||
} else {
|
||||
toClear = requestIds;
|
||||
}
|
||||
}
|
||||
|
||||
void performDelayedClear() {
|
||||
QMutexLocker lock(&toClearLock);
|
||||
if (!toClear.isEmpty()) {
|
||||
for (RPCCallbackClears::iterator i = toClear.begin(), e = toClear.end(); i != e; ++i) {
|
||||
if (cDebug()) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
if (parserMap.find(i->requestId) != parserMap.end()) {
|
||||
DEBUG_LOG(("RPC Info: clearing delayed callback %1, error code %2").arg(i->requestId).arg(i->errorCode));
|
||||
}
|
||||
}
|
||||
clearCallbacks(i->requestId, i->errorCode);
|
||||
internal::unregisterRequest(i->requestId);
|
||||
}
|
||||
toClear.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void execCallback(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) {
|
||||
RPCResponseHandler h;
|
||||
{
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
ParserMap::iterator i = parserMap.find(requestId);
|
||||
if (i != parserMap.cend()) {
|
||||
h = i.value();
|
||||
parserMap.erase(i);
|
||||
|
||||
DEBUG_LOG(("RPC Info: found parser for request %1, trying to parse response...").arg(requestId));
|
||||
}
|
||||
}
|
||||
if (h.onDone || h.onFail) {
|
||||
try {
|
||||
if (from >= end) throw mtpErrorInsufficient();
|
||||
|
||||
if (*from == mtpc_rpc_error) {
|
||||
RPCError err(MTPRpcError(from, end));
|
||||
DEBUG_LOG(("RPC Info: error received, code %1, type %2, description: %3").arg(err.code()).arg(err.type()).arg(err.description()));
|
||||
if (!rpcErrorOccured(requestId, h, err)) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
parserMap.insert(requestId, h);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (h.onDone) {
|
||||
// t_assert(App::app() != 0);
|
||||
(*h.onDone)(requestId, from, end);
|
||||
}
|
||||
}
|
||||
} catch (Exception &e) {
|
||||
if (!rpcErrorOccured(requestId, h, rpcClientError("RESPONSE_PARSE_FAILED", QString("exception text: ") + e.what()))) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
parserMap.insert(requestId, h);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG(("RPC Info: parser not found for %1").arg(requestId));
|
||||
}
|
||||
unregisterRequest(requestId);
|
||||
}
|
||||
|
||||
bool hasCallbacks(mtpRequestId requestId) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
ParserMap::iterator i = parserMap.find(requestId);
|
||||
return (i != parserMap.cend());
|
||||
}
|
||||
|
||||
void globalCallback(const mtpPrime *from, const mtpPrime *end) {
|
||||
if (globalHandler.onDone) (*globalHandler.onDone)(0, from, end); // some updates were received
|
||||
}
|
||||
|
||||
void onStateChange(int32 dcWithShift, int32 state) {
|
||||
if (stateChangedHandler) stateChangedHandler(dcWithShift, state);
|
||||
}
|
||||
|
||||
void onSessionReset(int32 dcWithShift) {
|
||||
if (sessionResetHandler) sessionResetHandler(dcWithShift);
|
||||
}
|
||||
|
||||
bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err) { // return true if need to clean request data
|
||||
if (mtpIsFlood(err)) {
|
||||
if (onFail && (*onFail)(requestId, err)) return true;
|
||||
}
|
||||
|
||||
if (onErrorDefault(requestId, err)) {
|
||||
return false;
|
||||
}
|
||||
LOG(("RPC Error: request %1 got fail with code %2, error %3%4").arg(requestId).arg(err.code()).arg(err.type()).arg(err.description().isEmpty() ? QString() : QString(": %1").arg(err.description())));
|
||||
onFail && (*onFail)(requestId, err);
|
||||
return true;
|
||||
}
|
||||
|
||||
GlobalSlotCarrier::GlobalSlotCarrier() {
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(checkDelayed()));
|
||||
}
|
||||
|
||||
void GlobalSlotCarrier::checkDelayed() {
|
||||
uint64 now = getms(true);
|
||||
while (!delayedRequests.isEmpty() && now >= delayedRequests.front().second) {
|
||||
mtpRequestId requestId = delayedRequests.front().first;
|
||||
delayedRequests.pop_front();
|
||||
|
||||
int32 dcWithShift = 0;
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::const_iterator i = requestsByDC.constFind(requestId);
|
||||
if (i != requestsByDC.cend()) {
|
||||
dcWithShift = i.value();
|
||||
} else {
|
||||
LOG(("MTP Error: could not find request dc for delayed resend, requestId %1").arg(requestId));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator j = requestMap.constFind(requestId);
|
||||
if (j == requestMap.cend()) {
|
||||
DEBUG_LOG(("MTP Error: could not find request %1").arg(requestId));
|
||||
continue;
|
||||
}
|
||||
req = j.value();
|
||||
}
|
||||
if (Session *session = getSession(qAbs(dcWithShift))) {
|
||||
session->sendPrepared(req);
|
||||
}
|
||||
}
|
||||
|
||||
if (!delayedRequests.isEmpty()) {
|
||||
_timer.start(delayedRequests.front().second - now);
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalSlotCarrier::connectionFinished(Connection *connection) {
|
||||
MTPQuittingConnections::iterator i = quittingConnections.find(connection);
|
||||
if (i != quittingConnections.cend()) {
|
||||
quittingConnections.erase(i);
|
||||
}
|
||||
|
||||
connection->waitTillFinish();
|
||||
delete connection;
|
||||
}
|
||||
|
||||
GlobalSlotCarrier *globalSlotCarrier() {
|
||||
return _globalSlotCarrier;
|
||||
}
|
||||
|
||||
void queueQuittingConnection(Connection *connection) {
|
||||
quittingConnections.insert(connection);
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
void start() {
|
||||
if (started()) return;
|
||||
|
||||
unixtimeInit();
|
||||
|
||||
internal::DcenterMap &dcs(internal::DCMap());
|
||||
|
||||
_globalSlotCarrier = new internal::GlobalSlotCarrier();
|
||||
|
||||
mainSession = new internal::Session(internal::mainDC());
|
||||
sessions.insert(mainSession->getDcWithShift(), mainSession);
|
||||
|
||||
_started = true;
|
||||
|
||||
if (internal::configNeeded()) {
|
||||
internal::configLoader()->load();
|
||||
}
|
||||
}
|
||||
|
||||
bool started() {
|
||||
return _started;
|
||||
}
|
||||
|
||||
void restart() {
|
||||
if (!_started) return;
|
||||
|
||||
for (auto i = sessions.cbegin(), e = sessions.cend(); i != e; ++i) {
|
||||
i.value()->restart();
|
||||
}
|
||||
}
|
||||
|
||||
void restart(int32 dcMask) {
|
||||
if (!_started) return;
|
||||
|
||||
dcMask = bareDcId(dcMask);
|
||||
for (Sessions::const_iterator i = sessions.cbegin(), e = sessions.cend(); i != e; ++i) {
|
||||
if (bareDcId(i.value()->getDcWithShift()) == dcMask) {
|
||||
i.value()->restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pause() {
|
||||
if (!_started) return;
|
||||
_paused = true;
|
||||
}
|
||||
|
||||
void unpause() {
|
||||
if (!_started) return;
|
||||
_paused = false;
|
||||
for (Sessions::const_iterator i = sessions.cbegin(), e = sessions.cend(); i != e; ++i) {
|
||||
i.value()->unpaused();
|
||||
}
|
||||
}
|
||||
|
||||
void configure(int32 dc, int32 user) {
|
||||
if (_started) return;
|
||||
internal::setDC(dc);
|
||||
internal::authed(user);
|
||||
}
|
||||
|
||||
void setdc(int32 dc, bool fromZeroOnly) {
|
||||
if (!dc || !_started) return;
|
||||
internal::setDC(dc, fromZeroOnly);
|
||||
int32 oldMainDc = mainSession->getDcWithShift();
|
||||
if (maindc() != oldMainDc) {
|
||||
killSession(oldMainDc);
|
||||
}
|
||||
Local::writeMtpData();
|
||||
}
|
||||
|
||||
int32 maindc() {
|
||||
return internal::mainDC();
|
||||
}
|
||||
|
||||
int32 dcstate(int32 dc) {
|
||||
if (!_started) return 0;
|
||||
|
||||
if (!dc) return mainSession->getState();
|
||||
if (!bareDcId(dc)) {
|
||||
dc += bareDcId(mainSession->getDcWithShift());
|
||||
}
|
||||
|
||||
Sessions::const_iterator i = sessions.constFind(dc);
|
||||
if (i != sessions.cend()) return i.value()->getState();
|
||||
|
||||
return DisconnectedState;
|
||||
}
|
||||
|
||||
QString dctransport(int32 dc) {
|
||||
if (!_started) return QString();
|
||||
|
||||
if (!dc) return mainSession->transport();
|
||||
if (!bareDcId(dc)) {
|
||||
dc += bareDcId(mainSession->getDcWithShift());
|
||||
}
|
||||
|
||||
Sessions::const_iterator i = sessions.constFind(dc);
|
||||
if (i != sessions.cend()) return i.value()->transport();
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void ping() {
|
||||
if (internal::Session *session = internal::getSession(0)) {
|
||||
session->ping();
|
||||
}
|
||||
}
|
||||
|
||||
void cancel(mtpRequestId requestId) {
|
||||
if (!_started) return;
|
||||
|
||||
mtpMsgId msgId = 0;
|
||||
requestsDelays.remove(requestId);
|
||||
{
|
||||
QWriteLocker locker(&requestMapLock);
|
||||
RequestMap::iterator i = requestMap.find(requestId);
|
||||
if (i != requestMap.end()) {
|
||||
msgId = *(mtpMsgId*)(i.value()->constData() + 4);
|
||||
requestMap.erase(i);
|
||||
}
|
||||
}
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i != requestsByDC.end()) {
|
||||
if (internal::Session *session = internal::getSession(qAbs(i.value()))) {
|
||||
session->cancel(requestId, msgId);
|
||||
}
|
||||
requestsByDC.erase(i);
|
||||
}
|
||||
}
|
||||
internal::clearCallbacks(requestId);
|
||||
}
|
||||
|
||||
void killSession(int32 dc) {
|
||||
Sessions::iterator i = sessions.find(dc);
|
||||
if (i != sessions.cend()) {
|
||||
bool wasMain = (i.value() == mainSession);
|
||||
|
||||
i.value()->kill();
|
||||
i.value()->deleteLater();
|
||||
sessions.erase(i);
|
||||
|
||||
if (wasMain) {
|
||||
mainSession = new internal::Session(internal::mainDC());
|
||||
int32 newdc = mainSession->getDcWithShift();
|
||||
i = sessions.find(newdc);
|
||||
if (i != sessions.cend()) {
|
||||
i.value()->kill();
|
||||
i.value()->deleteLater();
|
||||
sessions.erase(i);
|
||||
}
|
||||
sessions.insert(newdc, mainSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stopSession(int32 dc) {
|
||||
Sessions::iterator i = sessions.find(dc);
|
||||
if (i != sessions.end()) {
|
||||
if (i.value() != mainSession) { // don't stop main session
|
||||
i.value()->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 state(mtpRequestId requestId) {
|
||||
if (requestId > 0) {
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i != requestsByDC.end()) {
|
||||
if (internal::Session *session = internal::getSession(qAbs(i.value()))) {
|
||||
return session->requestState(requestId);
|
||||
}
|
||||
return MTP::RequestConnecting;
|
||||
}
|
||||
return MTP::RequestSent;
|
||||
}
|
||||
if (internal::Session *session = internal::getSession(-requestId)) {
|
||||
return session->requestState(0);
|
||||
}
|
||||
return MTP::RequestConnecting;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
for (Sessions::iterator i = sessions.begin(), e = sessions.end(); i != e; ++i) {
|
||||
i.value()->kill();
|
||||
delete i.value();
|
||||
}
|
||||
sessions.clear();
|
||||
mainSession = nullptr;
|
||||
|
||||
for_const (internal::Connection *connection, quittingConnections) {
|
||||
connection->waitTillFinish();
|
||||
delete connection;
|
||||
}
|
||||
quittingConnections.clear();
|
||||
|
||||
delete _globalSlotCarrier;
|
||||
_globalSlotCarrier = nullptr;
|
||||
|
||||
internal::destroyConfigLoader();
|
||||
|
||||
_started = false;
|
||||
}
|
||||
|
||||
void authed(int32 uid) {
|
||||
internal::authed(uid);
|
||||
}
|
||||
|
||||
int32 authedId() {
|
||||
return internal::authed();
|
||||
}
|
||||
|
||||
void logoutKeys(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail) {
|
||||
mtpRequestId req = MTP::send(MTPauth_LogOut(), onDone, onFail);
|
||||
internal::logoutOtherDCs();
|
||||
}
|
||||
|
||||
void setGlobalDoneHandler(RPCDoneHandlerPtr handler) {
|
||||
globalHandler.onDone = handler;
|
||||
}
|
||||
|
||||
void setGlobalFailHandler(RPCFailHandlerPtr handler) {
|
||||
globalHandler.onFail = handler;
|
||||
}
|
||||
|
||||
void setStateChangedHandler(MTPStateChangedHandler handler) {
|
||||
stateChangedHandler = handler;
|
||||
}
|
||||
|
||||
void setSessionResetHandler(MTPSessionResetHandler handler) {
|
||||
sessionResetHandler = handler;
|
||||
}
|
||||
|
||||
void clearGlobalHandlers() {
|
||||
setGlobalDoneHandler(RPCDoneHandlerPtr());
|
||||
setGlobalFailHandler(RPCFailHandlerPtr());
|
||||
setStateChangedHandler(0);
|
||||
setSessionResetHandler(0);
|
||||
}
|
||||
|
||||
void updateDcOptions(const QVector<MTPDcOption> &options) {
|
||||
internal::updateDcOptions(options);
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
AuthKeysMap getKeys() {
|
||||
return internal::getAuthKeys();
|
||||
}
|
||||
|
||||
void setKey(int32 dc, AuthKeyPtr key) {
|
||||
return internal::setAuthKey(dc, key);
|
||||
}
|
||||
|
||||
QReadWriteLock *dcOptionsMutex() {
|
||||
return internal::dcOptionsMutex();
|
||||
}
|
||||
|
||||
} // namespace MTP
|
240
Telegram/SourceFiles/mtproto/facade.h
Normal file
240
Telegram/SourceFiles/mtproto/facade.h
Normal file
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/core_types.h"
|
||||
#include "mtproto/session.h"
|
||||
#include "mtproto/file_download.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
namespace internal {
|
||||
|
||||
Session *getSession(ShiftedDcId shiftedDcId); // 0 - current set dc
|
||||
|
||||
bool paused();
|
||||
|
||||
void registerRequest(mtpRequestId requestId, int32 dc);
|
||||
void unregisterRequest(mtpRequestId requestId);
|
||||
|
||||
mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser);
|
||||
mtpRequest getRequest(mtpRequestId req);
|
||||
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest = 0);
|
||||
void clearCallbacks(mtpRequestId requestId, int32 errorCode = RPCError::NoError); // 0 - do not toggle onError callback
|
||||
void clearCallbacksDelayed(const RPCCallbackClears &requestIds);
|
||||
void performDelayedClear();
|
||||
void execCallback(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end);
|
||||
bool hasCallbacks(mtpRequestId requestId);
|
||||
void globalCallback(const mtpPrime *from, const mtpPrime *end);
|
||||
void onStateChange(int32 dcWithShift, int32 state);
|
||||
void onSessionReset(int32 dcWithShift);
|
||||
bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err); // return true if need to clean request data
|
||||
inline bool rpcErrorOccured(mtpRequestId requestId, const RPCResponseHandler &handler, const RPCError &err) {
|
||||
return rpcErrorOccured(requestId, handler.onFail, err);
|
||||
}
|
||||
|
||||
// used for:
|
||||
// - resending requests by timer which were postponed by flood delay
|
||||
// - destroying MTProtoConnections whose thread has finished
|
||||
class GlobalSlotCarrier : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
GlobalSlotCarrier();
|
||||
|
||||
public slots:
|
||||
|
||||
void checkDelayed();
|
||||
void connectionFinished(Connection *connection);
|
||||
|
||||
private:
|
||||
|
||||
SingleTimer _timer;
|
||||
};
|
||||
|
||||
GlobalSlotCarrier *globalSlotCarrier();
|
||||
void queueQuittingConnection(Connection *connection);
|
||||
|
||||
} // namespace internal
|
||||
|
||||
constexpr ShiftedDcId DCShift = 10000;
|
||||
constexpr DcId bareDcId(ShiftedDcId shiftedDcId) {
|
||||
return (shiftedDcId % DCShift);
|
||||
}
|
||||
constexpr ShiftedDcId shiftDcId(DcId dcId, int value) {
|
||||
return dcId + DCShift * value;
|
||||
}
|
||||
constexpr int getDcIdShift(ShiftedDcId shiftedDcId) {
|
||||
return (shiftedDcId - bareDcId(shiftedDcId)) / DCShift;
|
||||
}
|
||||
|
||||
// send(MTPhelp_GetConfig(), MTP::cfgDcId(dc)) - for dc enumeration
|
||||
constexpr ShiftedDcId cfgDcId(DcId dcId) {
|
||||
return shiftDcId(dcId, 0x01);
|
||||
}
|
||||
|
||||
// send(MTPauth_LogOut(), MTP::lgtDcId(dc)) - for logout of guest dcs enumeration
|
||||
constexpr ShiftedDcId lgtDcId(DcId dcId) {
|
||||
return shiftDcId(dcId, 0x02);
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
constexpr ShiftedDcId downloadDcId(DcId dcId, int index) {
|
||||
static_assert(MTPDownloadSessionsCount < 0x10, "Too large MTPDownloadSessionsCount!");
|
||||
return shiftDcId(dcId, 0x10 + index);
|
||||
};
|
||||
}
|
||||
|
||||
// send(req, callbacks, MTP::dldDcId(dc, index)) - for download shifted dc id
|
||||
inline ShiftedDcId dldDcId(DcId dcId, int index) {
|
||||
t_assert(index >= 0 && index < MTPDownloadSessionsCount);
|
||||
return internal::downloadDcId(dcId, index);
|
||||
}
|
||||
constexpr bool isDldDcId(ShiftedDcId shiftedDcId) {
|
||||
return (shiftedDcId >= internal::downloadDcId(0, 0)) && (shiftedDcId < internal::downloadDcId(0, MTPDownloadSessionsCount - 1) + DCShift);
|
||||
}
|
||||
|
||||
namespace internal {
|
||||
constexpr ShiftedDcId uploadDcId(DcId dcId, int index) {
|
||||
static_assert(MTPUploadSessionsCount < 0x10, "Too large MTPUploadSessionsCount!");
|
||||
return shiftDcId(dcId, 0x20 + index);
|
||||
};
|
||||
}
|
||||
|
||||
// send(req, callbacks, MTP::uplDcId(index)) - for upload shifted dc id
|
||||
// uploading always to the main dc so bareDcId == 0
|
||||
inline ShiftedDcId uplDcId(int index) {
|
||||
t_assert(index >= 0 && index < MTPUploadSessionsCount);
|
||||
return internal::uploadDcId(0, index);
|
||||
};
|
||||
constexpr bool isUplDcId(ShiftedDcId shiftedDcId) {
|
||||
return (shiftedDcId >= internal::uploadDcId(0, 0)) && (shiftedDcId < internal::uploadDcId(0, MTPUploadSessionsCount - 1) + DCShift);
|
||||
}
|
||||
|
||||
void start();
|
||||
bool started();
|
||||
void restart();
|
||||
void restart(int32 dcMask);
|
||||
|
||||
void pause();
|
||||
void unpause();
|
||||
|
||||
void configure(int32 dc, int32 user);
|
||||
|
||||
void setdc(int32 dc, bool fromZeroOnly = false);
|
||||
int32 maindc();
|
||||
|
||||
enum {
|
||||
DisconnectedState = 0,
|
||||
ConnectingState = 1,
|
||||
ConnectedState = 2,
|
||||
};
|
||||
int32 dcstate(int32 dc = 0);
|
||||
QString dctransport(int32 dc = 0);
|
||||
|
||||
template <typename TRequest>
|
||||
inline mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) {
|
||||
if (internal::Session *session = internal::getSession(dc)) {
|
||||
return session->send(request, callbacks, msCanWait, true, !dc, after);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
template <typename TRequest>
|
||||
inline mtpRequestId send(const TRequest &request, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail = RPCFailHandlerPtr(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) {
|
||||
return send(request, RPCResponseHandler(onDone, onFail), dc, msCanWait, after);
|
||||
}
|
||||
inline void sendAnything(int32 dc = 0, uint64 msCanWait = 0) {
|
||||
if (internal::Session *session = internal::getSession(dc)) {
|
||||
return session->sendAnything(msCanWait);
|
||||
}
|
||||
}
|
||||
void ping();
|
||||
void cancel(mtpRequestId req);
|
||||
void killSession(int32 dc);
|
||||
void stopSession(int32 dc);
|
||||
|
||||
enum {
|
||||
RequestSent = 0,
|
||||
RequestConnecting = 1,
|
||||
RequestSending = 2
|
||||
};
|
||||
int32 state(mtpRequestId req); // < 0 means waiting for such count of ms
|
||||
|
||||
void finish();
|
||||
|
||||
void authed(int32 uid);
|
||||
int32 authedId();
|
||||
void logoutKeys(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail);
|
||||
|
||||
void setGlobalDoneHandler(RPCDoneHandlerPtr handler);
|
||||
void setGlobalFailHandler(RPCFailHandlerPtr handler);
|
||||
void setStateChangedHandler(MTPStateChangedHandler handler);
|
||||
void setSessionResetHandler(MTPSessionResetHandler handler);
|
||||
void clearGlobalHandlers();
|
||||
|
||||
void updateDcOptions(const QVector<MTPDcOption> &options);
|
||||
|
||||
AuthKeysMap getKeys();
|
||||
void setKey(int32 dc, AuthKeyPtr key);
|
||||
|
||||
QReadWriteLock *dcOptionsMutex();
|
||||
|
||||
struct DcOption {
|
||||
DcOption(int id, MTPDdcOption::Flags flags, const string &ip, int port) : id(id), flags(flags), ip(ip), port(port) {
|
||||
}
|
||||
|
||||
int id;
|
||||
MTPDdcOption::Flags flags;
|
||||
string ip;
|
||||
int port;
|
||||
};
|
||||
typedef QMap<int, DcOption> DcOptions;
|
||||
|
||||
namespace internal {
|
||||
|
||||
template <typename TRequest>
|
||||
mtpRequestId Session::send(const TRequest &request, RPCResponseHandler callbacks, uint64 msCanWait, bool needsLayer, bool toMainDC, mtpRequestId after) {
|
||||
mtpRequestId requestId = 0;
|
||||
try {
|
||||
uint32 requestSize = request.innerLength() >> 2;
|
||||
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize));
|
||||
request.write(*reqSerialized);
|
||||
|
||||
DEBUG_LOG(("MTP Info: adding request to toSendMap, msCanWait %1").arg(msCanWait));
|
||||
|
||||
reqSerialized->msDate = getms(true); // > 0 - can send without container
|
||||
reqSerialized->needsLayer = needsLayer;
|
||||
if (after) reqSerialized->after = MTP::internal::getRequest(after);
|
||||
requestId = MTP::internal::storeRequest(reqSerialized, callbacks);
|
||||
|
||||
sendPrepared(reqSerialized, msCanWait);
|
||||
} catch (Exception &e) {
|
||||
requestId = 0;
|
||||
MTP::internal::rpcErrorOccured(requestId, callbacks, rpcClientError("NO_REQUEST_ID", QString("send() failed to queue request, exception: %1").arg(e.what())));
|
||||
}
|
||||
if (requestId) MTP::internal::registerRequest(requestId, toMainDC ? -getDcWithShift() : getDcWithShift());
|
||||
return requestId;
|
||||
}
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace MTP
|
|
@ -19,6 +19,9 @@ Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
|||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "mtproto/file_download.h"
|
||||
|
||||
#include "mainwidget.h"
|
||||
#include "window.h"
|
||||
|
||||
|
@ -349,9 +352,9 @@ mtpFileLoader::mtpFileLoader(const StorageImageLocation *location, int32 size, L
|
|||
, _location(location)
|
||||
, _id(0)
|
||||
, _access(0) {
|
||||
LoaderQueues::iterator i = queues.find(MTP::dld(0) + _dc);
|
||||
LoaderQueues::iterator i = queues.find(MTP::dldDcId(_dc, 0));
|
||||
if (i == queues.cend()) {
|
||||
i = queues.insert(MTP::dld(0) + _dc, FileLoaderQueue(MaxFileQueries));
|
||||
i = queues.insert(MTP::dldDcId(_dc, 0), FileLoaderQueue(MaxFileQueries));
|
||||
}
|
||||
_queue = &i.value();
|
||||
}
|
||||
|
@ -365,9 +368,9 @@ mtpFileLoader::mtpFileLoader(int32 dc, const uint64 &id, const uint64 &access, L
|
|||
, _location(0)
|
||||
, _id(id)
|
||||
, _access(access) {
|
||||
LoaderQueues::iterator i = queues.find(MTP::dld(0) + _dc);
|
||||
LoaderQueues::iterator i = queues.find(MTP::dldDcId(_dc, 0));
|
||||
if (i == queues.cend()) {
|
||||
i = queues.insert(MTP::dld(0) + _dc, FileLoaderQueue(MaxFileQueries));
|
||||
i = queues.insert(MTP::dldDcId(_dc, 0), FileLoaderQueue(MaxFileQueries));
|
||||
}
|
||||
_queue = &i.value();
|
||||
}
|
||||
|
@ -376,9 +379,33 @@ int32 mtpFileLoader::currentOffset(bool includeSkipped) const {
|
|||
return (_fileIsOpen ? _file.size() : _data.size()) - (includeSkipped ? 0 : _skippedBytes);
|
||||
}
|
||||
|
||||
namespace {
|
||||
QString serializereqs(const QMap<mtpRequestId, int32> &reqs) { // serialize requests map in json-like format
|
||||
QString result;
|
||||
result.reserve(reqs.size() * 16 + 4);
|
||||
result.append(qsl("{ "));
|
||||
for (auto i = reqs.cbegin(), e = reqs.cend(); i != e;) {
|
||||
result.append(QString::number(i.key())).append(qsl(" : ")).append(QString::number(i.value()));
|
||||
if (++i == e) {
|
||||
break;
|
||||
} else {
|
||||
result.append(qsl(", "));
|
||||
}
|
||||
}
|
||||
result.append(qsl(" }"));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
bool mtpFileLoader::loadPart() {
|
||||
if (_complete || _lastComplete || (!_requests.isEmpty() && !_size)) return false;
|
||||
if (_size && _nextRequestOffset >= _size) return false;
|
||||
if (_complete || _lastComplete || (!_requests.isEmpty() && !_size)) {
|
||||
if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): loadPart() returned, _complete=%2, _lastComplete=%3, _requests.size()=%4, _size=%5").arg(_id).arg(Logs::b(_complete)).arg(Logs::b(_lastComplete)).arg(_requests.size()).arg(_size));
|
||||
return false;
|
||||
}
|
||||
if (_size && _nextRequestOffset >= _size) {
|
||||
if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): loadPart() returned, _size=%2, _nextRequestOffset=%3, _requests=%4").arg(_id).arg(_size).arg(_nextRequestOffset).arg(serializereqs(_requests)));
|
||||
return false;
|
||||
}
|
||||
|
||||
int32 limit = DocumentDownloadPartSize;
|
||||
MTPInputFileLocation loc;
|
||||
|
@ -405,19 +432,28 @@ bool mtpFileLoader::loadPart() {
|
|||
|
||||
App::app()->killDownloadSessionsStop(_dc);
|
||||
|
||||
mtpRequestId reqId = MTP::send(MTPupload_GetFile(MTPupload_getFile(loc, MTP_int(offset), MTP_int(limit))), rpcDone(&mtpFileLoader::partLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::dld(dcIndex) + _dc, 50);
|
||||
mtpRequestId reqId = MTP::send(MTPupload_GetFile(MTPupload_getFile(loc, MTP_int(offset), MTP_int(limit))), rpcDone(&mtpFileLoader::partLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::dldDcId(_dc, dcIndex), 50);
|
||||
|
||||
++_queue->queries;
|
||||
dr.v[dcIndex] += limit;
|
||||
_requests.insert(reqId, dcIndex);
|
||||
_nextRequestOffset += limit;
|
||||
|
||||
if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): requested part with offset=%2, _queue->queries=%3, _nextRequestOffset=%4, _requests=%5").arg(_id).arg(offset).arg(_queue->queries).arg(_nextRequestOffset).arg(serializereqs(_requests)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRequestId req) {
|
||||
Requests::iterator i = _requests.find(req);
|
||||
if (i == _requests.cend()) return loadNext();
|
||||
if (i == _requests.cend()) {
|
||||
if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): request req=%2 for offset=%3 not found in _requests=%4").arg(_id).arg(req).arg(offset).arg(serializereqs(_requests)));
|
||||
return loadNext();
|
||||
}
|
||||
if (result.type() != mtpc_upload_file) {
|
||||
if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): bad cons received! %2").arg(_id).arg(result.type()));
|
||||
return cancel(true);
|
||||
}
|
||||
|
||||
int32 limit = (_locationType == UnknownFileLocation) ? DownloadPartSize : DocumentDownloadPartSize;
|
||||
int32 dcIndex = i.value();
|
||||
|
@ -428,6 +464,9 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
|
|||
|
||||
const MTPDupload_file &d(result.c_upload_file());
|
||||
const string &bytes(d.vbytes.c_string().v);
|
||||
|
||||
if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): got part with offset=%2, bytes=%3, _queue->queries=%4, _nextRequestOffset=%5, _requests=%6").arg(_id).arg(offset).arg(bytes.size()).arg(_queue->queries).arg(_nextRequestOffset).arg(serializereqs(_requests)));
|
||||
|
||||
if (bytes.size()) {
|
||||
if (_fileIsOpen) {
|
||||
int64 fsize = _file.size();
|
||||
|
@ -502,6 +541,8 @@ void mtpFileLoader::partLoaded(int32 offset, const MTPupload_File &result, mtpRe
|
|||
Local::writeImage(storageKey(*_location), StorageImageSaved(mtpToStorageType(_type), _data));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (DebugLogging::FileLoader() && _id) DEBUG_LOG(("FileLoader(%1): not done yet, _lastComplete=%2, _size=%3, _nextRequestOffset=%4, _requests=%5").arg(_id).arg(Logs::b(_lastComplete)).arg(_size).arg(_nextRequestOffset).arg(serializereqs(_requests)));
|
||||
}
|
||||
emit progress(this);
|
||||
loadNext();
|
||||
|
@ -733,9 +774,11 @@ private:
|
|||
};
|
||||
|
||||
void reinitWebLoadManager() {
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
if (webLoadManager()) {
|
||||
webLoadManager()->setProxySettings(App::getHttpProxySettings());
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void stopWebLoadManager() {
|
||||
|
@ -752,11 +795,13 @@ void stopWebLoadManager() {
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
void WebLoadManager::setProxySettings(const QNetworkProxy &proxy) {
|
||||
QMutexLocker lock(&_loaderPointersMutex);
|
||||
_proxySettings = proxy;
|
||||
emit proxyApplyDelayed();
|
||||
}
|
||||
#endif
|
||||
|
||||
WebLoadManager::WebLoadManager(QThread *thread) {
|
||||
moveToThread(thread);
|
||||
|
@ -940,27 +985,27 @@ void WebLoadManager::process() {
|
|||
i.value() = 0;
|
||||
}
|
||||
}
|
||||
for (Loaders::iterator i = _loaders.begin(), e = _loaders.end(); i != e;) {
|
||||
LoaderPointers::iterator it = _loaderPointers.find(i.key()->_interface);
|
||||
if (it != _loaderPointers.cend() && it.key()->_private != i.key()) {
|
||||
for (auto i = _loaders.begin(), e = _loaders.end(); i != e;) {
|
||||
LoaderPointers::iterator it = _loaderPointers.find((*i)->_interface);
|
||||
if (it != _loaderPointers.cend() && it.key()->_private != (*i)) {
|
||||
it = _loaderPointers.end();
|
||||
}
|
||||
if (it == _loaderPointers.cend()) {
|
||||
if (QNetworkReply *reply = i.key()->reply()) {
|
||||
if (QNetworkReply *reply = (*i)->reply()) {
|
||||
_replies.remove(reply);
|
||||
reply->abort();
|
||||
reply->deleteLater();
|
||||
}
|
||||
delete i.key();
|
||||
delete (*i);
|
||||
i = _loaders.erase(i);
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (Loaders::const_iterator i = newLoaders.cbegin(), e = newLoaders.cend(); i != e; ++i) {
|
||||
if (_loaders.contains(i.key())) {
|
||||
sendRequest(i.key());
|
||||
for_const (webFileLoaderPrivate *loader, newLoaders) {
|
||||
if (_loaders.contains(loader)) {
|
||||
sendRequest(loader);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -983,8 +1028,10 @@ void WebLoadManager::sendRequest(webFileLoaderPrivate *loader, const QString &re
|
|||
}
|
||||
|
||||
void WebLoadManager::proxyApply() {
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
QMutexLocker lock(&_loaderPointersMutex);
|
||||
_manager.setProxy(_proxySettings);
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebLoadManager::finish() {
|
||||
|
@ -1000,8 +1047,8 @@ void WebLoadManager::clear() {
|
|||
}
|
||||
_loaderPointers.clear();
|
||||
|
||||
for (Loaders::iterator i = _loaders.begin(), e = _loaders.end(); i != e; ++i) {
|
||||
delete i.key();
|
||||
for_const (webFileLoaderPrivate *loader, _loaders) {
|
||||
delete loader;
|
||||
}
|
||||
_loaders.clear();
|
||||
|
|
@ -321,7 +321,9 @@ public:
|
|||
|
||||
WebLoadManager(QThread *thread);
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
void setProxySettings(const QNetworkProxy &proxy);
|
||||
#endif
|
||||
|
||||
void append(webFileLoader *loader, const QString &url);
|
||||
void stop(webFileLoader *reader);
|
||||
|
@ -352,7 +354,9 @@ private:
|
|||
void sendRequest(webFileLoaderPrivate *loader, const QString &redirect = QString());
|
||||
bool handleReplyResult(webFileLoaderPrivate *loader, WebReplyProcessResult result);
|
||||
|
||||
#ifndef TDESKTOP_DISABLE_NETWORK_PROXY
|
||||
QNetworkProxy _proxySettings;
|
||||
#endif
|
||||
QNetworkAccessManager _manager;
|
||||
typedef QMap<webFileLoader*, webFileLoaderPrivate*> LoaderPointers;
|
||||
LoaderPointers _loaderPointers;
|
|
@ -21,6 +21,28 @@ Copyright (c) 2014 John Preston, https://desktop.telegram.org
|
|||
import glob
|
||||
import re
|
||||
|
||||
# define some checked flag convertions
|
||||
# the key flag type should be a subset of the value flag type
|
||||
# with exact the same names, then the key flag can be implicitly
|
||||
# casted to the value flag type
|
||||
parentFlags = {};
|
||||
parentFlagsList = [];
|
||||
def addChildParentFlags(child, parent):
|
||||
parentFlagsList.append(child);
|
||||
parentFlags[child] = parent;
|
||||
addChildParentFlags('MTPDmessageService', 'MTPDmessage');
|
||||
addChildParentFlags('MTPDupdateShortMessage', 'MTPDmessage');
|
||||
addChildParentFlags('MTPDupdateShortChatMessage', 'MTPDmessage');
|
||||
addChildParentFlags('MTPDupdateShortSentMessage', 'MTPDmessage');
|
||||
addChildParentFlags('MTPDreplyKeyboardHide', 'MTPDreplyKeyboardMarkup');
|
||||
addChildParentFlags('MTPDreplyKeyboardForceReply', 'MTPDreplyKeyboardMarkup');
|
||||
addChildParentFlags('MTPDinputPeerNotifySettings', 'MTPDpeerNotifySettings');
|
||||
addChildParentFlags('MTPDpeerNotifySettings', 'MTPDinputPeerNotifySettings');
|
||||
|
||||
# this is a map (key flags -> map (flag name -> flag bit))
|
||||
# each key flag of parentFlags should be a subset of the value flag here
|
||||
parentFlagsCheck = {};
|
||||
|
||||
funcs = 0
|
||||
types = 0;
|
||||
consts = 0
|
||||
|
@ -35,12 +57,13 @@ boxed = {};
|
|||
funcsText = '';
|
||||
typesText = '';
|
||||
dataTexts = '';
|
||||
creatorProxyText = '';
|
||||
inlineMethods = '';
|
||||
textSerializeInit = '';
|
||||
textSerializeMethods = '';
|
||||
forwards = '';
|
||||
forwTypedefs = '';
|
||||
out = open('mtpScheme.h', 'w')
|
||||
out = open('scheme_auto.h', 'w')
|
||||
out.write('/*\n');
|
||||
out.write('Created from \'/SourceFiles/mtproto/scheme.tl\' by \'/SourceFiles/mtproto/generate.py\' script\n\n');
|
||||
out.write('WARNING! All changes made in this file will be lost!\n\n');
|
||||
|
@ -63,7 +86,7 @@ out.write('\n');
|
|||
out.write('Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n');
|
||||
out.write('Copyright (c) 2014 John Preston, https://desktop.telegram.org\n');
|
||||
out.write('*/\n');
|
||||
out.write('#pragma once\n\n#include "mtpCoreTypes.h"\n');
|
||||
out.write('#pragma once\n\n#include "mtproto/core_types.h"\n');
|
||||
with open('scheme.tl') as f:
|
||||
for line in f:
|
||||
nocomment = re.match(r'^(.*?)//', line)
|
||||
|
@ -150,7 +173,10 @@ with open('scheme.tl') as f:
|
|||
continue;
|
||||
elif (ptypewide == '#'):
|
||||
hasFlags = pname;
|
||||
ptype = 'int';
|
||||
if funcsNow:
|
||||
ptype = 'flags<MTP' + name + '::Flags>';
|
||||
else:
|
||||
ptype = 'flags<MTPD' + name + '::Flags>';
|
||||
else:
|
||||
ptype = ptypewide;
|
||||
if (ptype.find('?') >= 0):
|
||||
|
@ -195,6 +221,27 @@ with open('scheme.tl') as f:
|
|||
prmsStr = [];
|
||||
prmsInit = [];
|
||||
prmsNames = [];
|
||||
if (len(conditions)):
|
||||
funcsText += '\tenum class Flag : int32 {\n';
|
||||
maxbit = 0;
|
||||
parentFlagsCheck['MTP' + name] = {};
|
||||
for paramName in conditionsList:
|
||||
funcsText += '\t\tf_' + paramName + ' = (1 << ' + conditions[paramName] + '),\n';
|
||||
parentFlagsCheck['MTP' + name][paramName] = conditions[paramName];
|
||||
maxbit = max(maxbit, int(conditions[paramName]));
|
||||
if (maxbit > 0):
|
||||
funcsText += '\n\t\tMAX_FIELD = (1 << ' + str(maxbit) + '),\n';
|
||||
funcsText += '\t};\n';
|
||||
funcsText += '\tQ_DECLARE_FLAGS(Flags, Flag);\n';
|
||||
funcsText += '\tfriend inline Flags operator~(Flag v) { return QFlag(~static_cast<int32>(v)); }\n';
|
||||
funcsText += '\n';
|
||||
for paramName in conditionsList:
|
||||
if (paramName in trivialConditions):
|
||||
funcsText += '\tbool is_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n';
|
||||
else:
|
||||
funcsText += '\tbool has_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n';
|
||||
funcsText += '\n';
|
||||
|
||||
if (len(prms) > len(trivialConditions)):
|
||||
for paramName in prmsList:
|
||||
if (paramName in trivialConditions):
|
||||
|
@ -207,7 +254,7 @@ with open('scheme.tl') as f:
|
|||
else:
|
||||
ptypeFull = 'MTP' + paramType;
|
||||
funcsText += '\t' + ptypeFull + ' v' + paramName + ';\n';
|
||||
if (paramType in ['int', 'Int', 'bool', 'Bool']):
|
||||
if (paramType in ['int', 'Int', 'bool', 'Bool', 'flags<Flags>']):
|
||||
prmsStr.append(ptypeFull + ' _' + paramName);
|
||||
else:
|
||||
prmsStr.append('const ' + ptypeFull + ' &_' + paramName);
|
||||
|
@ -218,19 +265,6 @@ with open('scheme.tl') as f:
|
|||
if (len(prms) > len(trivialConditions)):
|
||||
funcsText += '\tMTP' + name + '(' + ', '.join(prmsStr) + ') : ' + ', '.join(prmsInit) + ' {\n\t}\n';
|
||||
|
||||
if (len(conditions)):
|
||||
funcsText += '\n';
|
||||
funcsText += '\tenum {\n';
|
||||
for paramName in conditionsList:
|
||||
funcsText += '\t\tflag_' + paramName + ' = (1 << ' + conditions[paramName] + '),\n';
|
||||
funcsText += '\t};\n';
|
||||
funcsText += '\n';
|
||||
for paramName in conditionsList:
|
||||
if (paramName in trivialConditions):
|
||||
funcsText += '\tbool is_' + paramName + '() const { return v' + hasFlags + '.v & flag_' + paramName + '; }\n';
|
||||
else:
|
||||
funcsText += '\tbool has_' + paramName + '() const { return v' + hasFlags + '.v & flag_' + paramName + '; }\n';
|
||||
|
||||
funcsText += '\n';
|
||||
funcsText += '\tuint32 innerLength() const {\n'; # count size
|
||||
size = [];
|
||||
|
@ -274,6 +308,8 @@ with open('scheme.tl') as f:
|
|||
funcsText += '\n\ttypedef MTP' + resType + ' ResponseType;\n'; # method return type
|
||||
|
||||
funcsText += '};\n'; # class ending
|
||||
if (len(conditionsList)):
|
||||
funcsText += 'Q_DECLARE_OPERATORS_FOR_FLAGS(MTP' + name + '::Flags)\n\n';
|
||||
if (isTemplate != ''):
|
||||
funcsText += 'template <typename TQueryType>\n';
|
||||
funcsText += 'class MTP' + Name + ' : public MTPBoxed<MTP' + name + '<TQueryType> > {\n';
|
||||
|
@ -325,7 +361,9 @@ def addTextSerialize(lst, dct, dataLetter):
|
|||
conditions = data[6];
|
||||
trivialConditions = data[7];
|
||||
|
||||
result += 'void _serialize_' + name + '(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {\n';
|
||||
result += 'void _serialize_' + name + '(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {\n';
|
||||
if (len(conditions)):
|
||||
result += '\tMTP' + dataLetter + name + '::Flags flag(iflag);\n\n';
|
||||
if (len(prms)):
|
||||
result += '\tif (stage) {\n';
|
||||
result += '\t\tto.add(",\\n").addSpaces(lev);\n';
|
||||
|
@ -341,12 +379,12 @@ def addTextSerialize(lst, dct, dataLetter):
|
|||
if (k == hasFlags):
|
||||
result += 'if (start >= end) throw Exception("start >= end in flags"); else flags.back() = *start; ';
|
||||
if (k in trivialConditions):
|
||||
result += 'if (flag & MTP' + dataLetter + name + '::flag_' + k + ') { ';
|
||||
result += 'if (flag & MTP' + dataLetter + name + '::Flag::f_' + k + ') { ';
|
||||
result += 'to.add("YES [ BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); ';
|
||||
result += '} else { to.add("[ SKIPPED BY BIT ' + conditions[k] + ' IN FIELD ' + hasFlags + ' ]"); } ';
|
||||
else:
|
||||
if (k in conditions):
|
||||
result += 'if (flag & MTP' + dataLetter + name + '::flag_' + k + ') { ';
|
||||
result += 'if (flag & MTP' + dataLetter + name + '::Flag::f_' + k + ') { ';
|
||||
result += 'types.push_back(';
|
||||
vtypeget = re.match(r'^[Vv]ector<MTP([A-Za-z0-9\._]+)>', v);
|
||||
if (vtypeget):
|
||||
|
@ -383,7 +421,10 @@ def addTextSerialize(lst, dct, dataLetter):
|
|||
except KeyError:
|
||||
if (vtypeget):
|
||||
result += '); vtypes.push_back(';
|
||||
result += 'mtpc_' + restype;
|
||||
if (re.match(r'^flags<', restype)):
|
||||
result += 'mtpc_flags';
|
||||
else:
|
||||
result += 'mtpc_' + restype + '+0';
|
||||
if (not vtypeget):
|
||||
result += '); vtypes.push_back(0';
|
||||
else:
|
||||
|
@ -455,6 +496,28 @@ for restype in typesList:
|
|||
creatorParamsList = [];
|
||||
readText = '';
|
||||
writeText = '';
|
||||
|
||||
if (len(conditions)):
|
||||
dataText += '\tenum class Flag : int32 {\n';
|
||||
maxbit = 0;
|
||||
parentFlagsCheck['MTPD' + name] = {};
|
||||
for paramName in conditionsList:
|
||||
dataText += '\t\tf_' + paramName + ' = (1 << ' + conditions[paramName] + '),\n';
|
||||
parentFlagsCheck['MTPD' + name][paramName] = conditions[paramName];
|
||||
maxbit = max(maxbit, int(conditions[paramName]));
|
||||
if (maxbit > 0):
|
||||
dataText += '\n\t\tMAX_FIELD = (1 << ' + str(maxbit) + '),\n';
|
||||
dataText += '\t};\n';
|
||||
dataText += '\tQ_DECLARE_FLAGS(Flags, Flag);\n';
|
||||
dataText += '\tfriend inline Flags operator~(Flag v) { return QFlag(~static_cast<int32>(v)); }\n';
|
||||
dataText += '\n';
|
||||
for paramName in conditionsList:
|
||||
if (paramName in trivialConditions):
|
||||
dataText += '\tbool is_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n';
|
||||
else:
|
||||
dataText += '\tbool has_' + paramName + '() const { return v' + hasFlags + '.v & Flag::f_' + paramName + '; }\n';
|
||||
dataText += '\n';
|
||||
|
||||
dataText += '\tMTPD' + name + '() {\n\t}\n'; # default constructor
|
||||
switchLines += '\t\tcase mtpc_' + name + ': '; # for by-type-id type constructor
|
||||
if (len(prms) > len(trivialConditions)):
|
||||
|
@ -530,33 +593,26 @@ for restype in typesList:
|
|||
sizeFast = '\treturn 0;\n';
|
||||
|
||||
switchLines += 'break;\n';
|
||||
|
||||
if (len(conditions)):
|
||||
dataText += '\n';
|
||||
dataText += '\tenum {\n';
|
||||
for paramName in conditionsList:
|
||||
dataText += '\t\tflag_' + paramName + ' = (1 << ' + conditions[paramName] + '),\n';
|
||||
dataText += '\t};\n';
|
||||
dataText += '\n';
|
||||
for paramName in conditionsList:
|
||||
if (paramName in trivialConditions):
|
||||
dataText += '\tbool is_' + paramName + '() const { return v' + hasFlags + '.v & flag_' + paramName + '; }\n';
|
||||
else:
|
||||
dataText += '\tbool has_' + paramName + '() const { return v' + hasFlags + '.v & flag_' + paramName + '; }\n';
|
||||
dataText += '};\n'; # class ending
|
||||
|
||||
if (len(prms) > len(trivialConditions)):
|
||||
dataTexts += dataText; # add data class
|
||||
|
||||
friendDecl += '\tfriend MTP' + restype + ' MTP_' + name + '(' + ', '.join(creatorParams) + ');\n';
|
||||
creatorsText += 'inline MTP' + restype + ' MTP_' + name + '(' + ', '.join(creatorParams) + ') {\n';
|
||||
if (not friendDecl):
|
||||
friendDecl += '\tfriend class MTP::internal::TypeCreator;\n';
|
||||
creatorProxyText += '\tinline static MTP' + restype + ' new_' + name + '(' + ', '.join(creatorParams) + ') {\n';
|
||||
if (len(prms) > len(trivialConditions)): # creator with params
|
||||
creatorsText += '\treturn MTP' + restype + '(new MTPD' + name + '(' + ', '.join(creatorParamsList) + '));\n';
|
||||
creatorProxyText += '\t\treturn MTP' + restype + '(new MTPD' + name + '(' + ', '.join(creatorParamsList) + '));\n';
|
||||
else:
|
||||
if (withType): # creator by type
|
||||
creatorsText += '\treturn MTP' + restype + '(mtpc_' + name + ');\n';
|
||||
creatorProxyText += '\t\treturn MTP' + restype + '(mtpc_' + name + ');\n';
|
||||
else: # single creator
|
||||
creatorsText += '\treturn MTP' + restype + '();\n';
|
||||
creatorProxyText += '\t\treturn MTP' + restype + '();\n';
|
||||
creatorProxyText += '\t}\n';
|
||||
if (len(conditionsList)):
|
||||
creatorsText += 'Q_DECLARE_OPERATORS_FOR_FLAGS(MTPD' + name + '::Flags)\n';
|
||||
creatorsText += 'inline MTP' + restype + ' MTP_' + name + '(' + ', '.join(creatorParams) + ') {\n';
|
||||
creatorsText += '\treturn MTP::internal::TypeCreator::new_' + name + '(' + ', '.join(creatorParamsList) + ');\n';
|
||||
creatorsText += '}\n';
|
||||
|
||||
if (withType):
|
||||
|
@ -667,7 +723,7 @@ for restype in typesList:
|
|||
|
||||
typesText += '\tvoid write(mtpBuffer &to) const;\n'; # write method
|
||||
inlineMethods += 'inline void MTP' + restype + '::write(mtpBuffer &to) const {\n';
|
||||
if (withType):
|
||||
if (withType and writer != ''):
|
||||
inlineMethods += '\tswitch (_type) {\n';
|
||||
inlineMethods += writer;
|
||||
inlineMethods += '\t}\n';
|
||||
|
@ -705,8 +761,19 @@ for restype in typesList:
|
|||
inlineMethods += creatorsText;
|
||||
typesText += 'typedef MTPBoxed<MTP' + restype + '> MTP' + resType + ';\n'; # boxed type definition
|
||||
|
||||
for childName in parentFlagsList:
|
||||
parentName = parentFlags[childName];
|
||||
for flag in parentFlagsCheck[childName]:
|
||||
if (not flag in parentFlagsCheck[parentName]):
|
||||
print('Flag ' + flag + ' not found in ' + parentName + ' which should be a flags-parent of ' + childName);
|
||||
error
|
||||
elif (parentFlagsCheck[childName][flag] != parentFlagsCheck[parentName][flag]):
|
||||
print('Flag ' + flag + ' has different value in ' + parentName + ' which should be a flags-parent of ' + childName);
|
||||
error
|
||||
inlineMethods += 'inline ' + parentName + '::Flags mtpCastFlags(' + childName + '::Flags flags) { return ' + parentName + '::Flags(QFlag(flags)); }\n';
|
||||
|
||||
# manual types added here
|
||||
textSerializeMethods += 'void _serialize_rpc_result(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {\n';
|
||||
textSerializeMethods += 'void _serialize_rpc_result(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {\n';
|
||||
textSerializeMethods += '\tif (stage) {\n';
|
||||
textSerializeMethods += '\t\tto.add(",\\n").addSpaces(lev);\n';
|
||||
textSerializeMethods += '\t} else {\n';
|
||||
|
@ -721,7 +788,7 @@ textSerializeMethods += '\t}\n';
|
|||
textSerializeMethods += '}\n\n';
|
||||
textSerializeInit += '\t\t_serializers.insert(mtpc_rpc_result, _serialize_rpc_result);\n';
|
||||
|
||||
textSerializeMethods += 'void _serialize_msg_container(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {\n';
|
||||
textSerializeMethods += 'void _serialize_msg_container(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {\n';
|
||||
textSerializeMethods += '\tif (stage) {\n';
|
||||
textSerializeMethods += '\t\tto.add(",\\n").addSpaces(lev);\n';
|
||||
textSerializeMethods += '\t} else {\n';
|
||||
|
@ -735,7 +802,7 @@ textSerializeMethods += '\t}\n';
|
|||
textSerializeMethods += '}\n\n';
|
||||
textSerializeInit += '\t\t_serializers.insert(mtpc_msg_container, _serialize_msg_container);\n';
|
||||
|
||||
textSerializeMethods += 'void _serialize_core_message(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag) {\n';
|
||||
textSerializeMethods += 'void _serialize_core_message(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {\n';
|
||||
textSerializeMethods += '\tif (stage) {\n';
|
||||
textSerializeMethods += '\t\tto.add(",\\n").addSpaces(lev);\n';
|
||||
textSerializeMethods += '\t} else {\n';
|
||||
|
@ -786,16 +853,18 @@ textSerializeFull += '\t\t}\n';
|
|||
textSerializeFull += '\t}\n';
|
||||
textSerializeFull += '}\n';
|
||||
|
||||
out.write('\n// Creator proxy class declaration\nnamespace MTP {\nnamespace internal {\n\nclass TypeCreator;\n\n} // namespace internal\n} // namespace MTP\n');
|
||||
out.write('\n// Type id constants\nenum {\n' + ',\n'.join(enums) + '\n};\n');
|
||||
out.write('\n// Type forward declarations\n' + forwards);
|
||||
out.write('\n// Boxed types definitions\n' + forwTypedefs);
|
||||
out.write('\n// Type classes definitions\n' + typesText);
|
||||
out.write('\n// Type constructors with data\n' + dataTexts);
|
||||
out.write('\n// RPC methods\n' + funcsText);
|
||||
out.write('\n// Creator proxy class definition\nnamespace MTP {\nnamespace internal {\n\nclass TypeCreator {\npublic:\n' + creatorProxyText + '\t};\n\n} // namespace internal\n} // namespace MTP\n');
|
||||
out.write('\n// Inline methods definition\n' + inlineMethods);
|
||||
out.write('\n// Human-readable text serialization\nvoid mtpTextSerializeType(MTPStringLogger &to, const mtpPrime *&from, const mtpPrime *end, mtpPrime cons, uint32 level, mtpPrime vcons);\n');
|
||||
|
||||
outCpp = open('mtpScheme.cpp', 'w');
|
||||
outCpp = open('scheme_auto.cpp', 'w');
|
||||
outCpp.write('/*\n');
|
||||
outCpp.write('Created from \'/SourceFiles/mtproto/scheme.tl\' by \'/SourceFiles/mtproto/generate.py\' script\n\n');
|
||||
outCpp.write('WARNING! All changes made in this file will be lost!\n\n');
|
||||
|
@ -815,11 +884,11 @@ outCpp.write('\n');
|
|||
outCpp.write('Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n');
|
||||
outCpp.write('Copyright (c) 2014 John Preston, https://desktop.telegram.org\n');
|
||||
outCpp.write('*/\n');
|
||||
outCpp.write('#include "stdafx.h"\n#include "mtpScheme.h"\n\n');
|
||||
outCpp.write('#include "stdafx.h"\n\n#include "mtproto/scheme_auto.h"\n\n');
|
||||
outCpp.write('typedef QVector<mtpTypeId> Types;\ntypedef QVector<int32> StagesFlags;\n\n');
|
||||
outCpp.write(textSerializeMethods);
|
||||
outCpp.write('namespace {\n');
|
||||
outCpp.write('\ttypedef void(*mtpTextSerializer)(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 flag);\n');
|
||||
outCpp.write('\ttypedef void(*mtpTextSerializer)(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag);\n');
|
||||
outCpp.write('\ttypedef QMap<mtpTypeId, mtpTextSerializer> TextSerializers;\n\tTextSerializers _serializers;\n\n');
|
||||
outCpp.write('\tvoid initTextSerializers() {\n');
|
||||
outCpp.write(textSerializeInit);
|
||||
|
|
|
@ -1,911 +0,0 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#include "stdafx.h"
|
||||
#include "mtp.h"
|
||||
|
||||
#include "localstorage.h"
|
||||
|
||||
namespace {
|
||||
typedef QMap<int32, MTProtoSession*> Sessions;
|
||||
Sessions sessions;
|
||||
MTProtoSession *mainSession;
|
||||
|
||||
typedef QMap<mtpRequestId, int32> RequestsByDC; // holds dcWithShift for request to this dc or -dc for request to main dc
|
||||
RequestsByDC requestsByDC;
|
||||
QMutex requestByDCLock;
|
||||
|
||||
typedef QMap<mtpRequestId, int32> AuthExportRequests; // holds target dcWithShift for auth export request
|
||||
AuthExportRequests authExportRequests;
|
||||
|
||||
bool _started = false;
|
||||
|
||||
uint32 layer;
|
||||
|
||||
typedef QMap<mtpRequestId, RPCResponseHandler> ParserMap;
|
||||
ParserMap parserMap;
|
||||
QMutex parserMapLock;
|
||||
|
||||
typedef QMap<mtpRequestId, mtpRequest> RequestMap;
|
||||
RequestMap requestMap;
|
||||
QReadWriteLock requestMapLock;
|
||||
|
||||
typedef QPair<mtpRequestId, uint64> DelayedRequest;
|
||||
typedef QList<DelayedRequest> DelayedRequestsList;
|
||||
DelayedRequestsList delayedRequests;
|
||||
|
||||
typedef QMap<mtpRequestId, int32> RequestsDelays;
|
||||
RequestsDelays requestsDelays;
|
||||
|
||||
typedef QSet<mtpRequestId> BadGuestDCRequests;
|
||||
BadGuestDCRequests badGuestDCRequests;
|
||||
|
||||
typedef QVector<mtpRequestId> DCAuthWaiters;
|
||||
typedef QMap<int32, DCAuthWaiters> AuthWaiters; // holds request ids waiting for auth import to specific dc
|
||||
AuthWaiters authWaiters;
|
||||
|
||||
typedef OrderedSet<MTProtoConnection*> MTPQuittingConnections;
|
||||
MTPQuittingConnections quittingConnections;
|
||||
|
||||
QMutex toClearLock;
|
||||
RPCCallbackClears toClear;
|
||||
|
||||
RPCResponseHandler globalHandler;
|
||||
MTPStateChangedHandler stateChangedHandler = 0;
|
||||
MTPSessionResetHandler sessionResetHandler = 0;
|
||||
_mtp_internal::GlobalSlotCarrier *_globalSlotCarrier = 0;
|
||||
|
||||
void importDone(const MTPauth_Authorization &result, mtpRequestId req) {
|
||||
QMutexLocker locker1(&requestByDCLock);
|
||||
|
||||
RequestsByDC::iterator i = requestsByDC.find(req);
|
||||
if (i == requestsByDC.end()) {
|
||||
LOG(("MTP Error: auth import request not found in requestsByDC, requestId: %1").arg(req));
|
||||
RPCError error(rpcClientError("AUTH_IMPORT_FAIL", QString("did not find import request in requestsByDC, request %1").arg(req)));
|
||||
if (globalHandler.onFail && MTP::authedId()) (*globalHandler.onFail)(req, error); // auth failed in main dc
|
||||
return;
|
||||
}
|
||||
int32 newdc = i.value() % _mtp_internal::dcShift;
|
||||
|
||||
DEBUG_LOG(("MTP Info: auth import to dc %1 succeeded").arg(newdc));
|
||||
|
||||
DCAuthWaiters &waiters(authWaiters[newdc]);
|
||||
if (waiters.size()) {
|
||||
QReadLocker locker(&requestMapLock);
|
||||
for (DCAuthWaiters::iterator i = waiters.begin(), e = waiters.end(); i != e; ++i) {
|
||||
mtpRequestId requestId = *i;
|
||||
RequestMap::const_iterator j = requestMap.constFind(requestId);
|
||||
if (j == requestMap.cend()) {
|
||||
LOG(("MTP Error: could not find request %1 for resending").arg(requestId));
|
||||
continue;
|
||||
}
|
||||
int32 dcWithShift = newdc;
|
||||
{
|
||||
RequestsByDC::iterator k = requestsByDC.find(requestId);
|
||||
if (k == requestsByDC.cend()) {
|
||||
LOG(("MTP Error: could not find request %1 by dc for resending").arg(requestId));
|
||||
continue;
|
||||
}
|
||||
if (k.value() < 0) {
|
||||
MTP::setdc(newdc);
|
||||
k.value() = -newdc;
|
||||
} else {
|
||||
int32 shift = k.value() - (k.value() % _mtp_internal::dcShift);
|
||||
dcWithShift += shift;
|
||||
k.value() = dcWithShift;
|
||||
}
|
||||
DEBUG_LOG(("MTP Info: resending request %1 to dc %2 after import auth").arg(requestId).arg(k.value()));
|
||||
}
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(dcWithShift)) {
|
||||
session->sendPrepared(j.value());
|
||||
}
|
||||
}
|
||||
waiters.clear();
|
||||
}
|
||||
}
|
||||
|
||||
bool importFail(const RPCError &error, mtpRequestId req) {
|
||||
if (mtpIsFlood(error)) return false;
|
||||
|
||||
if (globalHandler.onFail && MTP::authedId()) (*globalHandler.onFail)(req, error); // auth import failed
|
||||
return true;
|
||||
}
|
||||
|
||||
void exportDone(const MTPauth_ExportedAuthorization &result, mtpRequestId req) {
|
||||
AuthExportRequests::const_iterator i = authExportRequests.constFind(req);
|
||||
if (i == authExportRequests.cend()) {
|
||||
LOG(("MTP Error: auth export request target dcWithShift not found, requestId: %1").arg(req));
|
||||
RPCError error(rpcClientError("AUTH_IMPORT_FAIL", QString("did not find target dcWithShift, request %1").arg(req)));
|
||||
if (globalHandler.onFail && MTP::authedId()) (*globalHandler.onFail)(req, error); // auth failed in main dc
|
||||
return;
|
||||
}
|
||||
|
||||
const MTPDauth_exportedAuthorization &data(result.c_auth_exportedAuthorization());
|
||||
MTP::send(MTPauth_ImportAuthorization(data.vid, data.vbytes), rpcDone(importDone), rpcFail(importFail), i.value());
|
||||
authExportRequests.remove(req);
|
||||
}
|
||||
|
||||
bool exportFail(const RPCError &error, mtpRequestId req) {
|
||||
if (mtpIsFlood(error)) return false;
|
||||
|
||||
AuthExportRequests::const_iterator i = authExportRequests.constFind(req);
|
||||
if (i != authExportRequests.cend()) {
|
||||
authWaiters[i.value() % _mtp_internal::dcShift].clear();
|
||||
}
|
||||
if (globalHandler.onFail && MTP::authedId()) (*globalHandler.onFail)(req, error); // auth failed in main dc
|
||||
return true;
|
||||
}
|
||||
|
||||
bool onErrorDefault(mtpRequestId requestId, const RPCError &error) {
|
||||
const QString &err(error.type());
|
||||
int32 code = error.code();
|
||||
if (!mtpIsFlood(error) && err != qsl("AUTH_KEY_UNREGISTERED")) {
|
||||
int breakpoint = 0;
|
||||
}
|
||||
bool badGuestDC = (code == 400) && (err == qsl("FILE_ID_INVALID"));
|
||||
QRegularExpressionMatch m;
|
||||
if ((m = QRegularExpression("^(FILE|PHONE|NETWORK|USER)_MIGRATE_(\\d+)$").match(err)).hasMatch()) {
|
||||
if (!requestId) return false;
|
||||
|
||||
int32 dcWithShift = 0, newdcWithShift = m.captured(2).toInt();
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i == requestsByDC.end()) {
|
||||
LOG(("MTP Error: could not find request %1 for migrating to %2").arg(requestId).arg(newdcWithShift));
|
||||
} else {
|
||||
dcWithShift = i.value();
|
||||
}
|
||||
}
|
||||
if (!dcWithShift || !newdcWithShift) return false;
|
||||
|
||||
DEBUG_LOG(("MTP Info: changing request %1 from dcWithShift%2 to dc%3").arg(requestId).arg(dcWithShift).arg(newdcWithShift));
|
||||
if (dcWithShift < 0) { // newdc shift = 0
|
||||
if (false && MTP::authedId() && !authExportRequests.contains(requestId)) { // migrate not supported at this moment
|
||||
DEBUG_LOG(("MTP Info: importing auth to dc %1").arg(newdcWithShift));
|
||||
DCAuthWaiters &waiters(authWaiters[newdcWithShift]);
|
||||
if (!waiters.size()) {
|
||||
authExportRequests.insert(MTP::send(MTPauth_ExportAuthorization(MTP_int(newdcWithShift)), rpcDone(exportDone), rpcFail(exportFail)), newdcWithShift);
|
||||
}
|
||||
waiters.push_back(requestId);
|
||||
return true;
|
||||
} else {
|
||||
MTP::setdc(newdcWithShift);
|
||||
}
|
||||
} else {
|
||||
int32 shift = dcWithShift - (dcWithShift % _mtp_internal::dcShift);
|
||||
newdcWithShift += shift;
|
||||
}
|
||||
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator i = requestMap.constFind(requestId);
|
||||
if (i == requestMap.cend()) {
|
||||
LOG(("MTP Error: could not find request %1").arg(requestId));
|
||||
return false;
|
||||
}
|
||||
req = i.value();
|
||||
}
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(newdcWithShift)) {
|
||||
_mtp_internal::registerRequest(requestId, (dcWithShift < 0) ? -newdcWithShift : newdcWithShift);
|
||||
session->sendPrepared(req);
|
||||
}
|
||||
return true;
|
||||
} else if (code < 0 || code >= 500 || (m = QRegularExpression("^FLOOD_WAIT_(\\d+)$").match(err)).hasMatch()) {
|
||||
if (!requestId) return false;
|
||||
|
||||
int32 secs = 1;
|
||||
if (code < 0 || code >= 500) {
|
||||
RequestsDelays::iterator i = requestsDelays.find(requestId);
|
||||
if (i != requestsDelays.cend()) {
|
||||
secs = (i.value() > 60) ? i.value() : (i.value() *= 2);
|
||||
} else {
|
||||
requestsDelays.insert(requestId, secs);
|
||||
}
|
||||
} else {
|
||||
secs = m.captured(1).toInt();
|
||||
if (secs >= 60) return false;
|
||||
}
|
||||
uint64 sendAt = getms(true) + secs * 1000 + 10;
|
||||
DelayedRequestsList::iterator i = delayedRequests.begin(), e = delayedRequests.end();
|
||||
for (; i != e; ++i) {
|
||||
if (i->first == requestId) return true;
|
||||
if (i->second > sendAt) break;
|
||||
}
|
||||
delayedRequests.insert(i, DelayedRequest(requestId, sendAt));
|
||||
|
||||
if (_globalSlotCarrier) _globalSlotCarrier->checkDelayed();
|
||||
|
||||
return true;
|
||||
} else if (code == 401 || (badGuestDC && badGuestDCRequests.constFind(requestId) == badGuestDCRequests.cend())) {
|
||||
int32 dcWithShift = 0;
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i != requestsByDC.end()) {
|
||||
dcWithShift = i.value();
|
||||
} else {
|
||||
LOG(("MTP Error: unauthorized request without dc info, requestId %1").arg(requestId));
|
||||
}
|
||||
}
|
||||
int32 newdc = abs(dcWithShift) % _mtp_internal::dcShift;
|
||||
if (!newdc || newdc == mtpMainDC() || !MTP::authedId()) {
|
||||
if (!badGuestDC && globalHandler.onFail) (*globalHandler.onFail)(requestId, error); // auth failed in main dc
|
||||
return false;
|
||||
}
|
||||
|
||||
DEBUG_LOG(("MTP Info: importing auth to dcWithShift %1").arg(dcWithShift));
|
||||
DCAuthWaiters &waiters(authWaiters[newdc]);
|
||||
if (!waiters.size()) {
|
||||
authExportRequests.insert(MTP::send(MTPauth_ExportAuthorization(MTP_int(newdc)), rpcDone(exportDone), rpcFail(exportFail)), abs(dcWithShift));
|
||||
}
|
||||
waiters.push_back(requestId);
|
||||
if (badGuestDC) badGuestDCRequests.insert(requestId);
|
||||
return true;
|
||||
} else if (err == qsl("CONNECTION_NOT_INITED") || err == qsl("CONNECTION_LAYER_INVALID")) {
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator i = requestMap.constFind(requestId);
|
||||
if (i == requestMap.cend()) {
|
||||
LOG(("MTP Error: could not find request %1").arg(requestId));
|
||||
return false;
|
||||
}
|
||||
req = i.value();
|
||||
}
|
||||
int32 dcWithShift = 0;
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i == requestsByDC.end()) {
|
||||
LOG(("MTP Error: could not find request %1 for resending with init connection").arg(requestId));
|
||||
} else {
|
||||
dcWithShift = i.value();
|
||||
}
|
||||
}
|
||||
if (!dcWithShift) return false;
|
||||
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(dcWithShift < 0 ? (-dcWithShift) : dcWithShift)) {
|
||||
req->needsLayer = true;
|
||||
session->sendPrepared(req);
|
||||
}
|
||||
return true;
|
||||
} else if (err == qsl("MSG_WAIT_FAILED")) {
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator i = requestMap.constFind(requestId);
|
||||
if (i == requestMap.cend()) {
|
||||
LOG(("MTP Error: could not find request %1").arg(requestId));
|
||||
return false;
|
||||
}
|
||||
req = i.value();
|
||||
}
|
||||
if (!req->after) {
|
||||
LOG(("MTP Error: wait failed for not dependent request %1").arg(requestId));
|
||||
return false;
|
||||
}
|
||||
int32 dcWithShift = 0;
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId), j = requestsByDC.find(req->after->requestId);
|
||||
if (i == requestsByDC.end()) {
|
||||
LOG(("MTP Error: could not find request %1 by dc").arg(requestId));
|
||||
} else if (j == requestsByDC.end()) {
|
||||
LOG(("MTP Error: could not find dependent request %1 by dc").arg(req->after->requestId));
|
||||
} else {
|
||||
dcWithShift = i.value();
|
||||
if (i.value() != j.value()) {
|
||||
req->after = mtpRequest();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!dcWithShift) return false;
|
||||
|
||||
if (!req->after) {
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(dcWithShift < 0 ? (-dcWithShift) : dcWithShift)) {
|
||||
req->needsLayer = true;
|
||||
session->sendPrepared(req);
|
||||
}
|
||||
} else {
|
||||
int32 newdc = abs(dcWithShift) % _mtp_internal::dcShift;
|
||||
DCAuthWaiters &waiters(authWaiters[newdc]);
|
||||
if (waiters.indexOf(req->after->requestId) >= 0) {
|
||||
if (waiters.indexOf(requestId) < 0) {
|
||||
waiters.push_back(requestId);
|
||||
}
|
||||
if (badGuestDCRequests.constFind(req->after->requestId) != badGuestDCRequests.cend()) {
|
||||
if (badGuestDCRequests.constFind(requestId) == badGuestDCRequests.cend()) {
|
||||
badGuestDCRequests.insert(requestId);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint64 at = 0;
|
||||
DelayedRequestsList::iterator i = delayedRequests.begin(), e = delayedRequests.end();
|
||||
for (; i != e; ++i) {
|
||||
if (i->first == requestId) return true;
|
||||
if (i->first == req->after->requestId) break;
|
||||
}
|
||||
if (i != e) {
|
||||
delayedRequests.insert(i, DelayedRequest(requestId, i->second));
|
||||
}
|
||||
|
||||
if (_globalSlotCarrier) _globalSlotCarrier->checkDelayed();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (badGuestDC) badGuestDCRequests.remove(requestId);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _paused = false;
|
||||
|
||||
}
|
||||
|
||||
namespace _mtp_internal {
|
||||
MTProtoSession *getSession(int32 dcWithShift) {
|
||||
if (!_started) return 0;
|
||||
if (!dcWithShift) return mainSession;
|
||||
if (!(dcWithShift % _mtp_internal::dcShift)) {
|
||||
dcWithShift += (mainSession->getDcWithShift() % _mtp_internal::dcShift);
|
||||
}
|
||||
|
||||
Sessions::const_iterator i = sessions.constFind(dcWithShift);
|
||||
if (i == sessions.cend()) {
|
||||
i = sessions.insert(dcWithShift, new MTProtoSession(dcWithShift));
|
||||
}
|
||||
return i.value();
|
||||
}
|
||||
|
||||
bool paused() {
|
||||
return _paused;
|
||||
}
|
||||
|
||||
void registerRequest(mtpRequestId requestId, int32 dcWithShift) {
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
requestsByDC.insert(requestId, dcWithShift);
|
||||
}
|
||||
_mtp_internal::performDelayedClear(); // need to do it somewhere..
|
||||
}
|
||||
|
||||
void unregisterRequest(mtpRequestId requestId) {
|
||||
requestsDelays.remove(requestId);
|
||||
|
||||
{
|
||||
QWriteLocker locker(&requestMapLock);
|
||||
requestMap.remove(requestId);
|
||||
}
|
||||
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
requestsByDC.remove(requestId);
|
||||
}
|
||||
|
||||
mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser) {
|
||||
mtpRequestId res = reqid();
|
||||
request->requestId = res;
|
||||
if (parser.onDone || parser.onFail) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
parserMap.insert(res, parser);
|
||||
}
|
||||
{
|
||||
QWriteLocker locker(&requestMapLock);
|
||||
requestMap.insert(res, request);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
mtpRequest getRequest(mtpRequestId reqId) {
|
||||
static mtpRequest zero;
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator i = requestMap.constFind(reqId);
|
||||
req = (i == requestMap.cend()) ? zero : i.value();
|
||||
}
|
||||
return req;
|
||||
}
|
||||
|
||||
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest) {
|
||||
mtpMsgId afterId(*(mtpMsgId*)(from->after->data() + 4));
|
||||
mtpRequestMap::const_iterator i = afterId ? haveSent.constFind(afterId) : haveSent.cend();
|
||||
int32 size = to->size(), lenInInts = (from.innerLength() >> 2), headlen = 4, fulllen = headlen + lenInInts;
|
||||
if (i == haveSent.constEnd()) { // no invoke after or such msg was not sent or was completed recently
|
||||
to->resize(size + fulllen + skipBeforeRequest);
|
||||
if (skipBeforeRequest) {
|
||||
memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime));
|
||||
memcpy(to->data() + size + headlen + skipBeforeRequest, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime));
|
||||
} else {
|
||||
memcpy(to->data() + size, from->constData() + 4, fulllen * sizeof(mtpPrime));
|
||||
}
|
||||
} else {
|
||||
to->resize(size + fulllen + skipBeforeRequest + 3);
|
||||
memcpy(to->data() + size, from->constData() + 4, headlen * sizeof(mtpPrime));
|
||||
(*to)[size + 3] += 3 * sizeof(mtpPrime);
|
||||
*((mtpTypeId*)&((*to)[size + headlen + skipBeforeRequest])) = mtpc_invokeAfterMsg;
|
||||
memcpy(to->data() + size + headlen + skipBeforeRequest + 1, &afterId, 2 * sizeof(mtpPrime));
|
||||
memcpy(to->data() + size + headlen + skipBeforeRequest + 3, from->constData() + 4 + headlen, lenInInts * sizeof(mtpPrime));
|
||||
if (size + 3 != 7) (*to)[7] += 3 * sizeof(mtpPrime);
|
||||
}
|
||||
}
|
||||
|
||||
void clearCallbacks(mtpRequestId requestId, int32 errorCode) {
|
||||
RPCResponseHandler h;
|
||||
bool found = false;
|
||||
{
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
ParserMap::iterator i = parserMap.find(requestId);
|
||||
if (i != parserMap.end()) {
|
||||
h = i.value();
|
||||
found = true;
|
||||
|
||||
parserMap.erase(i);
|
||||
}
|
||||
}
|
||||
if (errorCode && found) {
|
||||
rpcErrorOccured(requestId, h, rpcClientError("CLEAR_CALLBACK", QString("did not handle request %1, error code %2").arg(requestId).arg(errorCode)));
|
||||
}
|
||||
}
|
||||
|
||||
void clearCallbacksDelayed(const RPCCallbackClears &requestIds) {
|
||||
uint32 idsCount = requestIds.size();
|
||||
if (!idsCount) return;
|
||||
|
||||
if (cDebug()) {
|
||||
QString idsStr = QString("%1").arg(requestIds[0].requestId);
|
||||
for (uint32 i = 1; i < idsCount; ++i) {
|
||||
idsStr += QString(", %1").arg(requestIds[i].requestId);
|
||||
}
|
||||
DEBUG_LOG(("RPC Info: clear callbacks delayed, msgIds: %1").arg(idsStr));
|
||||
}
|
||||
|
||||
QMutexLocker lock(&toClearLock);
|
||||
uint32 toClearNow = toClear.size();
|
||||
if (toClearNow) {
|
||||
toClear.resize(toClearNow + idsCount);
|
||||
memcpy(toClear.data() + toClearNow, requestIds.constData(), idsCount * sizeof(RPCCallbackClear));
|
||||
} else {
|
||||
toClear = requestIds;
|
||||
}
|
||||
}
|
||||
|
||||
void performDelayedClear() {
|
||||
QMutexLocker lock(&toClearLock);
|
||||
if (!toClear.isEmpty()) {
|
||||
for (RPCCallbackClears::iterator i = toClear.begin(), e = toClear.end(); i != e; ++i) {
|
||||
if (cDebug()) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
if (parserMap.find(i->requestId) != parserMap.end()) {
|
||||
DEBUG_LOG(("RPC Info: clearing delayed callback %1, error code %2").arg(i->requestId).arg(i->errorCode));
|
||||
}
|
||||
}
|
||||
clearCallbacks(i->requestId, i->errorCode);
|
||||
_mtp_internal::unregisterRequest(i->requestId);
|
||||
}
|
||||
toClear.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void execCallback(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end) {
|
||||
RPCResponseHandler h;
|
||||
{
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
ParserMap::iterator i = parserMap.find(requestId);
|
||||
if (i != parserMap.cend()) {
|
||||
h = i.value();
|
||||
parserMap.erase(i);
|
||||
|
||||
DEBUG_LOG(("RPC Info: found parser for request %1, trying to parse response..").arg(requestId));
|
||||
}
|
||||
}
|
||||
if (h.onDone || h.onFail) {
|
||||
try {
|
||||
if (from >= end) throw mtpErrorInsufficient();
|
||||
|
||||
if (*from == mtpc_rpc_error) {
|
||||
RPCError err(MTPRpcError(from, end));
|
||||
DEBUG_LOG(("RPC Info: error received, code %1, type %2, description: %3").arg(err.code()).arg(err.type()).arg(err.description()));
|
||||
if (!rpcErrorOccured(requestId, h, err)) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
parserMap.insert(requestId, h);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (h.onDone) {
|
||||
// t_assert(App::app() != 0);
|
||||
(*h.onDone)(requestId, from, end);
|
||||
}
|
||||
}
|
||||
} catch (Exception &e) {
|
||||
if (!rpcErrorOccured(requestId, h, rpcClientError("RESPONSE_PARSE_FAILED", QString("exception text: ") + e.what()))) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
parserMap.insert(requestId, h);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
DEBUG_LOG(("RPC Info: parser not found for %1").arg(requestId));
|
||||
}
|
||||
unregisterRequest(requestId);
|
||||
}
|
||||
|
||||
bool hasCallbacks(mtpRequestId requestId) {
|
||||
QMutexLocker locker(&parserMapLock);
|
||||
ParserMap::iterator i = parserMap.find(requestId);
|
||||
return (i != parserMap.cend());
|
||||
}
|
||||
|
||||
void globalCallback(const mtpPrime *from, const mtpPrime *end) {
|
||||
if (globalHandler.onDone) (*globalHandler.onDone)(0, from, end); // some updates were received
|
||||
}
|
||||
|
||||
void onStateChange(int32 dcWithShift, int32 state) {
|
||||
if (stateChangedHandler) stateChangedHandler(dcWithShift, state);
|
||||
}
|
||||
|
||||
void onSessionReset(int32 dcWithShift) {
|
||||
if (sessionResetHandler) sessionResetHandler(dcWithShift);
|
||||
}
|
||||
|
||||
bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err) { // return true if need to clean request data
|
||||
if (mtpIsFlood(err)) {
|
||||
if (onFail && (*onFail)(requestId, err)) return true;
|
||||
}
|
||||
|
||||
if (onErrorDefault(requestId, err)) {
|
||||
return false;
|
||||
}
|
||||
LOG(("RPC Error: request %1 got fail with code %2, error %3%4").arg(requestId).arg(err.code()).arg(err.type()).arg(err.description().isEmpty() ? QString() : QString(": %1").arg(err.description())));
|
||||
onFail && (*onFail)(requestId, err);
|
||||
return true;
|
||||
}
|
||||
|
||||
GlobalSlotCarrier::GlobalSlotCarrier() {
|
||||
connect(&_timer, SIGNAL(timeout()), this, SLOT(checkDelayed()));
|
||||
}
|
||||
|
||||
void GlobalSlotCarrier::checkDelayed() {
|
||||
uint64 now = getms(true);
|
||||
while (!delayedRequests.isEmpty() && now >= delayedRequests.front().second) {
|
||||
mtpRequestId requestId = delayedRequests.front().first;
|
||||
delayedRequests.pop_front();
|
||||
|
||||
int32 dcWithShift = 0;
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::const_iterator i = requestsByDC.constFind(requestId);
|
||||
if (i != requestsByDC.cend()) {
|
||||
dcWithShift = i.value();
|
||||
} else {
|
||||
LOG(("MTP Error: could not find request dc for delayed resend, requestId %1").arg(requestId));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
mtpRequest req;
|
||||
{
|
||||
QReadLocker locker(&requestMapLock);
|
||||
RequestMap::const_iterator j = requestMap.constFind(requestId);
|
||||
if (j == requestMap.cend()) {
|
||||
DEBUG_LOG(("MTP Error: could not find request %1").arg(requestId));
|
||||
continue;
|
||||
}
|
||||
req = j.value();
|
||||
}
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(dcWithShift < 0 ? (-dcWithShift) : dcWithShift)) {
|
||||
session->sendPrepared(req);
|
||||
}
|
||||
}
|
||||
|
||||
if (!delayedRequests.isEmpty()) {
|
||||
_timer.start(delayedRequests.front().second - now);
|
||||
}
|
||||
}
|
||||
|
||||
void GlobalSlotCarrier::connectionFinished(MTProtoConnection *connection) {
|
||||
MTPQuittingConnections::iterator i = quittingConnections.find(connection);
|
||||
if (i != quittingConnections.cend()) {
|
||||
quittingConnections.erase(i);
|
||||
}
|
||||
|
||||
connection->waitTillFinish();
|
||||
delete connection;
|
||||
}
|
||||
|
||||
GlobalSlotCarrier *globalSlotCarrier() {
|
||||
return _globalSlotCarrier;
|
||||
}
|
||||
|
||||
void queueQuittingConnection(MTProtoConnection *connection) {
|
||||
quittingConnections.insert(connection);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
namespace MTP {
|
||||
|
||||
const uint32 cfg = 1 * _mtp_internal::dcShift; // send(MTPhelp_GetConfig(), MTP::cfg + dc) - for dc enum
|
||||
const uint32 lgt = 2 * _mtp_internal::dcShift; // send(MTPauth_LogOut(), MTP::lgt + dc) - for logout of guest dcs enum
|
||||
const uint32 dldStart = dld(0), dldEnd = dld(MTPDownloadSessionsCount - 1) + _mtp_internal::dcShift;
|
||||
const uint32 uplStart = upl(0), uplEnd = upl(MTPUploadSessionsCount - 1) + _mtp_internal::dcShift;
|
||||
|
||||
void start() {
|
||||
if (started()) return;
|
||||
|
||||
unixtimeInit();
|
||||
|
||||
MTProtoDCMap &dcs(mtpDCMap());
|
||||
|
||||
_globalSlotCarrier = new _mtp_internal::GlobalSlotCarrier();
|
||||
|
||||
mainSession = new MTProtoSession(mtpMainDC());
|
||||
sessions.insert(mainSession->getDcWithShift(), mainSession);
|
||||
|
||||
_started = true;
|
||||
|
||||
if (mtpNeedConfig()) {
|
||||
mtpConfigLoader()->load();
|
||||
}
|
||||
}
|
||||
|
||||
bool started() {
|
||||
return _started;
|
||||
}
|
||||
|
||||
void restart() {
|
||||
if (!_started) return;
|
||||
|
||||
for (Sessions::const_iterator i = sessions.cbegin(), e = sessions.cend(); i != e; ++i) {
|
||||
i.value()->restart();
|
||||
}
|
||||
}
|
||||
|
||||
void restart(int32 dcMask) {
|
||||
if (!_started) return;
|
||||
|
||||
dcMask %= _mtp_internal::dcShift;
|
||||
for (Sessions::const_iterator i = sessions.cbegin(), e = sessions.cend(); i != e; ++i) {
|
||||
if ((i.value()->getDcWithShift() % int(_mtp_internal::dcShift)) == dcMask) {
|
||||
i.value()->restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pause() {
|
||||
if (!_started) return;
|
||||
_paused = true;
|
||||
}
|
||||
|
||||
void unpause() {
|
||||
if (!_started) return;
|
||||
_paused = false;
|
||||
for (Sessions::const_iterator i = sessions.cbegin(), e = sessions.cend(); i != e; ++i) {
|
||||
i.value()->unpaused();
|
||||
}
|
||||
}
|
||||
|
||||
void configure(int32 dc, int32 user) {
|
||||
if (_started) return;
|
||||
mtpSetDC(dc);
|
||||
mtpAuthed(user);
|
||||
}
|
||||
|
||||
void setdc(int32 dc, bool fromZeroOnly) {
|
||||
if (!dc || !_started) return;
|
||||
mtpSetDC(dc, fromZeroOnly);
|
||||
int32 oldMainDc = mainSession->getDcWithShift();
|
||||
if (maindc() != oldMainDc) {
|
||||
killSession(oldMainDc);
|
||||
}
|
||||
Local::writeMtpData();
|
||||
}
|
||||
|
||||
int32 maindc() {
|
||||
return mtpMainDC();
|
||||
}
|
||||
|
||||
int32 dcstate(int32 dc) {
|
||||
if (!_started) return 0;
|
||||
|
||||
if (!dc) return mainSession->getState();
|
||||
if (!(dc % _mtp_internal::dcShift)) {
|
||||
dc += (mainSession->getDcWithShift() % _mtp_internal::dcShift);
|
||||
}
|
||||
|
||||
Sessions::const_iterator i = sessions.constFind(dc);
|
||||
if (i != sessions.cend()) return i.value()->getState();
|
||||
|
||||
return MTProtoConnection::Disconnected;
|
||||
}
|
||||
|
||||
QString dctransport(int32 dc) {
|
||||
if (!_started) return QString();
|
||||
|
||||
if (!dc) return mainSession->transport();
|
||||
if (!(dc % _mtp_internal::dcShift)) {
|
||||
dc += (mainSession->getDcWithShift() % _mtp_internal::dcShift);
|
||||
}
|
||||
|
||||
Sessions::const_iterator i = sessions.constFind(dc);
|
||||
if (i != sessions.cend()) return i.value()->transport();
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void ping() {
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(0)) {
|
||||
session->ping();
|
||||
}
|
||||
}
|
||||
|
||||
void cancel(mtpRequestId requestId) {
|
||||
if (!_started) return;
|
||||
|
||||
mtpMsgId msgId = 0;
|
||||
requestsDelays.remove(requestId);
|
||||
{
|
||||
QWriteLocker locker(&requestMapLock);
|
||||
RequestMap::iterator i = requestMap.find(requestId);
|
||||
if (i != requestMap.end()) {
|
||||
msgId = *(mtpMsgId*)(i.value()->constData() + 4);
|
||||
requestMap.erase(i);
|
||||
}
|
||||
}
|
||||
{
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i != requestsByDC.end()) {
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(abs(i.value()))) {
|
||||
session->cancel(requestId, msgId);
|
||||
}
|
||||
requestsByDC.erase(i);
|
||||
}
|
||||
}
|
||||
_mtp_internal::clearCallbacks(requestId);
|
||||
}
|
||||
|
||||
void killSession(int32 dc) {
|
||||
Sessions::iterator i = sessions.find(dc);
|
||||
if (i != sessions.cend()) {
|
||||
bool wasMain = (i.value() == mainSession);
|
||||
|
||||
i.value()->kill();
|
||||
i.value()->deleteLater();
|
||||
sessions.erase(i);
|
||||
|
||||
if (wasMain) {
|
||||
mainSession = new MTProtoSession(mtpMainDC());
|
||||
int32 newdc = mainSession->getDcWithShift();
|
||||
i = sessions.find(newdc);
|
||||
if (i != sessions.cend()) {
|
||||
i.value()->kill();
|
||||
i.value()->deleteLater();
|
||||
sessions.erase(i);
|
||||
}
|
||||
sessions.insert(newdc, mainSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stopSession(int32 dc) {
|
||||
Sessions::iterator i = sessions.find(dc);
|
||||
if (i != sessions.end()) {
|
||||
if (i.value() != mainSession) { // don't stop main session
|
||||
i.value()->stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32 state(mtpRequestId requestId) {
|
||||
if (requestId > 0) {
|
||||
QMutexLocker locker(&requestByDCLock);
|
||||
RequestsByDC::iterator i = requestsByDC.find(requestId);
|
||||
if (i != requestsByDC.end()) {
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(abs(i.value()))) {
|
||||
return session->requestState(requestId);
|
||||
}
|
||||
return MTP::RequestConnecting;
|
||||
}
|
||||
return MTP::RequestSent;
|
||||
}
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(-requestId)) {
|
||||
return session->requestState(0);
|
||||
}
|
||||
return MTP::RequestConnecting;
|
||||
}
|
||||
|
||||
void finish() {
|
||||
for (Sessions::iterator i = sessions.begin(), e = sessions.end(); i != e; ++i) {
|
||||
i.value()->kill();
|
||||
delete i.value();
|
||||
}
|
||||
sessions.clear();
|
||||
mainSession = nullptr;
|
||||
|
||||
for (MTPQuittingConnections::const_iterator i = quittingConnections.cbegin(), e = quittingConnections.cend(); i != e; ++i) {
|
||||
i.key()->waitTillFinish();
|
||||
delete i.key();
|
||||
}
|
||||
quittingConnections.clear();
|
||||
|
||||
delete _globalSlotCarrier;
|
||||
_globalSlotCarrier = nullptr;
|
||||
|
||||
mtpDestroyConfigLoader();
|
||||
|
||||
_started = false;
|
||||
}
|
||||
|
||||
void authed(int32 uid) {
|
||||
mtpAuthed(uid);
|
||||
}
|
||||
|
||||
int32 authedId() {
|
||||
return mtpAuthed();
|
||||
}
|
||||
|
||||
void logoutKeys(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail) {
|
||||
mtpRequestId req = MTP::send(MTPauth_LogOut(), onDone, onFail);
|
||||
mtpLogoutOtherDCs();
|
||||
}
|
||||
|
||||
void setGlobalDoneHandler(RPCDoneHandlerPtr handler) {
|
||||
globalHandler.onDone = handler;
|
||||
}
|
||||
|
||||
void setGlobalFailHandler(RPCFailHandlerPtr handler) {
|
||||
globalHandler.onFail = handler;
|
||||
}
|
||||
|
||||
void setStateChangedHandler(MTPStateChangedHandler handler) {
|
||||
stateChangedHandler = handler;
|
||||
}
|
||||
|
||||
void setSessionResetHandler(MTPSessionResetHandler handler) {
|
||||
sessionResetHandler = handler;
|
||||
}
|
||||
|
||||
void clearGlobalHandlers() {
|
||||
setGlobalDoneHandler(RPCDoneHandlerPtr());
|
||||
setGlobalFailHandler(RPCFailHandlerPtr());
|
||||
setStateChangedHandler(0);
|
||||
setSessionResetHandler(0);
|
||||
}
|
||||
|
||||
void updateDcOptions(const QVector<MTPDcOption> &options) {
|
||||
mtpUpdateDcOptions(options);
|
||||
Local::writeSettings();
|
||||
}
|
||||
|
||||
mtpKeysMap getKeys() {
|
||||
return mtpGetKeys();
|
||||
}
|
||||
|
||||
void setKey(int32 dc, mtpAuthKeyPtr key) {
|
||||
return mtpSetKey(dc, key);
|
||||
}
|
||||
|
||||
QReadWriteLock *dcOptionsMutex() {
|
||||
return mtpDcOptionsMutex();
|
||||
}
|
||||
|
||||
};
|
|
@ -1,163 +0,0 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/mtpSession.h"
|
||||
#include "mtproto/mtpFileLoader.h"
|
||||
|
||||
namespace _mtp_internal {
|
||||
MTProtoSession *getSession(int32 dc); // 0 - current set dc
|
||||
|
||||
bool paused();
|
||||
|
||||
void registerRequest(mtpRequestId requestId, int32 dc);
|
||||
void unregisterRequest(mtpRequestId requestId);
|
||||
|
||||
static const uint32 dcShift = 10000;
|
||||
|
||||
mtpRequestId storeRequest(mtpRequest &request, const RPCResponseHandler &parser);
|
||||
mtpRequest getRequest(mtpRequestId req);
|
||||
void wrapInvokeAfter(mtpRequest &to, const mtpRequest &from, const mtpRequestMap &haveSent, int32 skipBeforeRequest = 0);
|
||||
void clearCallbacks(mtpRequestId requestId, int32 errorCode = RPCError::NoError); // 0 - do not toggle onError callback
|
||||
void clearCallbacksDelayed(const RPCCallbackClears &requestIds);
|
||||
void performDelayedClear();
|
||||
void execCallback(mtpRequestId requestId, const mtpPrime *from, const mtpPrime *end);
|
||||
bool hasCallbacks(mtpRequestId requestId);
|
||||
void globalCallback(const mtpPrime *from, const mtpPrime *end);
|
||||
void onStateChange(int32 dcWithShift, int32 state);
|
||||
void onSessionReset(int32 dcWithShift);
|
||||
bool rpcErrorOccured(mtpRequestId requestId, const RPCFailHandlerPtr &onFail, const RPCError &err); // return true if need to clean request data
|
||||
inline bool rpcErrorOccured(mtpRequestId requestId, const RPCResponseHandler &handler, const RPCError &err) {
|
||||
return rpcErrorOccured(requestId, handler.onFail, err);
|
||||
}
|
||||
|
||||
// used for:
|
||||
// - resending requests by timer which were postponed by flood delay
|
||||
// - destroying MTProtoConnections whose thread has finished
|
||||
class GlobalSlotCarrier : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
GlobalSlotCarrier();
|
||||
|
||||
public slots:
|
||||
|
||||
void checkDelayed();
|
||||
void connectionFinished(MTProtoConnection *connection);
|
||||
|
||||
private:
|
||||
|
||||
SingleTimer _timer;
|
||||
};
|
||||
|
||||
GlobalSlotCarrier *globalSlotCarrier();
|
||||
void queueQuittingConnection(MTProtoConnection *connection);
|
||||
};
|
||||
|
||||
namespace MTP {
|
||||
|
||||
extern const uint32 cfg; // send(MTPhelp_GetConfig(), MTP::cfg + dc) - for dc enum
|
||||
extern const uint32 lgt; // send(MTPauth_LogOut(), MTP::lgt + dc) - for logout of guest dcs enum
|
||||
inline uint32 dld(int32 index) { // send(req, callbacks, MTP::dld(i) + dc) - for download
|
||||
t_assert(index >= 0 && index < MTPDownloadSessionsCount);
|
||||
return (0x10 + index) * _mtp_internal::dcShift;
|
||||
};
|
||||
inline uint32 upl(int32 index) { // send(req, callbacks, MTP::upl(i) + dc) - for upload
|
||||
t_assert(index >= 0 && index < MTPUploadSessionsCount);
|
||||
return (0x20 + index) * _mtp_internal::dcShift;
|
||||
};
|
||||
extern const uint32 dldStart, dldEnd; // dc >= dldStart && dc < dldEnd => dc in dld
|
||||
extern const uint32 uplStart, uplEnd; // dc >= uplStart && dc < uplEnd => dc in upl
|
||||
|
||||
void start();
|
||||
bool started();
|
||||
void restart();
|
||||
void restart(int32 dcMask);
|
||||
|
||||
void pause();
|
||||
void unpause();
|
||||
|
||||
void configure(int32 dc, int32 user);
|
||||
|
||||
void setdc(int32 dc, bool fromZeroOnly = false);
|
||||
int32 maindc();
|
||||
|
||||
int32 dcstate(int32 dc = 0);
|
||||
QString dctransport(int32 dc = 0);
|
||||
|
||||
template <typename TRequest>
|
||||
inline mtpRequestId send(const TRequest &request, RPCResponseHandler callbacks = RPCResponseHandler(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) {
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(dc)) {
|
||||
return session->send(request, callbacks, msCanWait, true, !dc, after);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
template <typename TRequest>
|
||||
inline mtpRequestId send(const TRequest &request, RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail = RPCFailHandlerPtr(), int32 dc = 0, uint64 msCanWait = 0, mtpRequestId after = 0) {
|
||||
return send(request, RPCResponseHandler(onDone, onFail), dc, msCanWait, after);
|
||||
}
|
||||
inline void sendAnything(int32 dc = 0, uint64 msCanWait = 0) {
|
||||
if (MTProtoSession *session = _mtp_internal::getSession(dc)) {
|
||||
return session->sendAnything(msCanWait);
|
||||
}
|
||||
}
|
||||
void ping();
|
||||
void cancel(mtpRequestId req);
|
||||
void killSession(int32 dc);
|
||||
void stopSession(int32 dc);
|
||||
|
||||
enum {
|
||||
RequestSent = 0,
|
||||
RequestConnecting = 1,
|
||||
RequestSending = 2
|
||||
};
|
||||
int32 state(mtpRequestId req); // < 0 means waiting for such count of ms
|
||||
|
||||
void finish();
|
||||
|
||||
void authed(int32 uid);
|
||||
int32 authedId();
|
||||
void logoutKeys(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFail);
|
||||
|
||||
void setGlobalDoneHandler(RPCDoneHandlerPtr handler);
|
||||
void setGlobalFailHandler(RPCFailHandlerPtr handler);
|
||||
void setStateChangedHandler(MTPStateChangedHandler handler);
|
||||
void setSessionResetHandler(MTPSessionResetHandler handler);
|
||||
void clearGlobalHandlers();
|
||||
|
||||
void updateDcOptions(const QVector<MTPDcOption> &options);
|
||||
|
||||
template <typename T>
|
||||
T nonce() {
|
||||
T result;
|
||||
memset_rand(&result, sizeof(T));
|
||||
return result;
|
||||
}
|
||||
|
||||
mtpKeysMap getKeys();
|
||||
void setKey(int32 dc, mtpAuthKeyPtr key);
|
||||
|
||||
QReadWriteLock *dcOptionsMutex();
|
||||
|
||||
};
|
||||
|
||||
#include "mtproto/mtpSessionImpl.h"
|
|
@ -1,524 +0,0 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "mtproto/mtpCoreTypes.h"
|
||||
#include "mtproto/mtpPublicRSA.h"
|
||||
#include "mtproto/mtpAuthKey.h"
|
||||
|
||||
inline bool mtpRequestData::isSentContainer(const mtpRequest &request) { // "request-like" wrap for msgIds vector
|
||||
if (request->size() < 9) return false;
|
||||
return (!request->msDate && !(*request)[6]); // msDate = 0, seqNo = 0
|
||||
}
|
||||
inline bool mtpRequestData::isStateRequest(const mtpRequest &request) {
|
||||
if (request->size() < 9) return false;
|
||||
return (mtpTypeId((*request)[8]) == mtpc_msgs_state_req);
|
||||
}
|
||||
inline bool mtpRequestData::needAck(const mtpRequest &request) {
|
||||
if (request->size() < 9) return false;
|
||||
return mtpRequestData::needAckByType((*request)[8]);
|
||||
}
|
||||
inline bool mtpRequestData::needAckByType(mtpTypeId type) {
|
||||
switch (type) {
|
||||
case mtpc_msg_container:
|
||||
case mtpc_msgs_ack:
|
||||
case mtpc_http_wait:
|
||||
case mtpc_bad_msg_notification:
|
||||
case mtpc_msgs_all_info:
|
||||
case mtpc_msgs_state_info:
|
||||
case mtpc_msg_detailed_info:
|
||||
case mtpc_msg_new_detailed_info:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class MTProtoConnectionPrivate;
|
||||
class MTPSessionData;
|
||||
|
||||
class MTPThread : public QThread {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MTPThread();
|
||||
uint32 getThreadId() const;
|
||||
~MTPThread();
|
||||
|
||||
private:
|
||||
uint32 _threadId;
|
||||
|
||||
};
|
||||
|
||||
class MTProtoConnection {
|
||||
public:
|
||||
|
||||
enum ConnectionType {
|
||||
TcpConnection,
|
||||
HttpConnection
|
||||
};
|
||||
|
||||
MTProtoConnection();
|
||||
int32 start(MTPSessionData *data, int32 dc = 0); // return dc
|
||||
void kill();
|
||||
void waitTillFinish();
|
||||
~MTProtoConnection();
|
||||
|
||||
enum {
|
||||
Disconnected = 0,
|
||||
Connecting = 1,
|
||||
Connected = 2,
|
||||
|
||||
UpdateAlways = 666
|
||||
};
|
||||
|
||||
int32 state() const;
|
||||
QString transport() const;
|
||||
|
||||
private:
|
||||
|
||||
QThread *thread;
|
||||
MTProtoConnectionPrivate *data;
|
||||
|
||||
};
|
||||
|
||||
class MTPabstractConnection : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
typedef QList<mtpBuffer> BuffersQueue;
|
||||
|
||||
public:
|
||||
|
||||
MTPabstractConnection() : _sentEncrypted(false) {
|
||||
}
|
||||
|
||||
void setSentEncrypted() {
|
||||
_sentEncrypted = true;
|
||||
}
|
||||
|
||||
virtual void sendData(mtpBuffer &buffer) = 0; // has size + 3, buffer[0] = len, buffer[1] = packetnum, buffer[last] = crc32
|
||||
virtual void disconnectFromServer() = 0;
|
||||
virtual void connectTcp(const QString &addr, int32 port, int32 flags) = 0;
|
||||
virtual void connectHttp(const QString &addr, int32 port, int32 flags) = 0;
|
||||
virtual bool isConnected() const = 0;
|
||||
virtual bool usingHttpWait() {
|
||||
return false;
|
||||
}
|
||||
virtual bool needHttpWait() {
|
||||
return false;
|
||||
}
|
||||
|
||||
virtual int32 debugState() const = 0;
|
||||
|
||||
virtual QString transport() const = 0;
|
||||
|
||||
BuffersQueue &received() {
|
||||
return receivedQueue;
|
||||
}
|
||||
|
||||
signals:
|
||||
|
||||
void receivedData();
|
||||
void receivedSome(); // to stop restart timer
|
||||
|
||||
void error(bool mayBeBadKey = false);
|
||||
|
||||
void connected();
|
||||
void disconnected();
|
||||
|
||||
protected:
|
||||
|
||||
BuffersQueue receivedQueue; // list of received packets, not processed yet
|
||||
bool _sentEncrypted;
|
||||
|
||||
};
|
||||
|
||||
class MTPabstractTcpConnection : public MTPabstractConnection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MTPabstractTcpConnection();
|
||||
|
||||
public slots:
|
||||
|
||||
void socketRead();
|
||||
|
||||
protected:
|
||||
|
||||
QTcpSocket sock;
|
||||
uint32 packetNum; // sent packet number
|
||||
|
||||
uint32 packetRead, packetLeft; // reading from socket
|
||||
bool readingToShort;
|
||||
char *currentPos;
|
||||
mtpBuffer longBuffer;
|
||||
mtpPrime shortBuffer[MTPShortBufferSize];
|
||||
virtual void socketPacket(const char *packet, uint32 length) = 0;
|
||||
|
||||
};
|
||||
|
||||
class MTPautoConnection : public MTPabstractTcpConnection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MTPautoConnection(QThread *thread);
|
||||
|
||||
void sendData(mtpBuffer &buffer);
|
||||
void disconnectFromServer();
|
||||
void connectTcp(const QString &addr, int32 port, int32 flags);
|
||||
void connectHttp(const QString &addr, int32 port, int32 flags);
|
||||
bool isConnected() const;
|
||||
bool usingHttpWait();
|
||||
bool needHttpWait();
|
||||
|
||||
int32 debugState() const;
|
||||
|
||||
QString transport() const;
|
||||
|
||||
public slots:
|
||||
|
||||
void socketError(QAbstractSocket::SocketError e);
|
||||
void requestFinished(QNetworkReply *reply);
|
||||
|
||||
void onSocketConnected();
|
||||
void onSocketDisconnected();
|
||||
void onHttpStart();
|
||||
|
||||
void onTcpTimeoutTimer();
|
||||
|
||||
protected:
|
||||
|
||||
void socketPacket(const char *packet, uint32 length);
|
||||
|
||||
private:
|
||||
|
||||
void tcpSend(mtpBuffer &buffer);
|
||||
void httpSend(mtpBuffer &buffer);
|
||||
enum Status {
|
||||
WaitingBoth = 0,
|
||||
WaitingHttp,
|
||||
WaitingTcp,
|
||||
HttpReady,
|
||||
UsingHttp,
|
||||
UsingTcp,
|
||||
FinishedWork
|
||||
};
|
||||
Status status;
|
||||
MTPint128 tcpNonce, httpNonce;
|
||||
QTimer httpStartTimer;
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
QUrl address;
|
||||
|
||||
typedef QSet<QNetworkReply*> Requests;
|
||||
Requests requests;
|
||||
|
||||
QString _addrTcp, _addrHttp;
|
||||
int32 _portTcp, _portHttp, _flagsTcp, _flagsHttp;
|
||||
int32 _tcpTimeout;
|
||||
QTimer tcpTimeoutTimer;
|
||||
|
||||
};
|
||||
|
||||
class MTPtcpConnection : public MTPabstractTcpConnection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MTPtcpConnection(QThread *thread);
|
||||
|
||||
void sendData(mtpBuffer &buffer);
|
||||
void disconnectFromServer();
|
||||
void connectTcp(const QString &addr, int32 port, int32 flags);
|
||||
void connectHttp(const QString &addr, int32 port, int32 flags) { // not supported
|
||||
}
|
||||
bool isConnected() const;
|
||||
|
||||
int32 debugState() const;
|
||||
|
||||
QString transport() const;
|
||||
|
||||
public slots:
|
||||
|
||||
void socketError(QAbstractSocket::SocketError e);
|
||||
|
||||
void onSocketConnected();
|
||||
void onSocketDisconnected();
|
||||
|
||||
void onTcpTimeoutTimer();
|
||||
|
||||
protected:
|
||||
|
||||
void socketPacket(const char *packet, uint32 length);
|
||||
|
||||
private:
|
||||
|
||||
enum Status {
|
||||
WaitingTcp = 0,
|
||||
UsingTcp,
|
||||
FinishedWork
|
||||
};
|
||||
Status status;
|
||||
MTPint128 tcpNonce;
|
||||
|
||||
QString _addr;
|
||||
int32 _port, _tcpTimeout, _flags;
|
||||
QTimer tcpTimeoutTimer;
|
||||
|
||||
};
|
||||
|
||||
class MTPhttpConnection : public MTPabstractConnection {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MTPhttpConnection(QThread *thread);
|
||||
|
||||
void sendData(mtpBuffer &buffer);
|
||||
void disconnectFromServer();
|
||||
void connectTcp(const QString &addr, int32 port, int32 flags) { // not supported
|
||||
}
|
||||
void connectHttp(const QString &addr, int32 port, int32 flags);
|
||||
bool isConnected() const;
|
||||
bool usingHttpWait();
|
||||
bool needHttpWait();
|
||||
|
||||
int32 debugState() const;
|
||||
|
||||
QString transport() const;
|
||||
|
||||
public slots:
|
||||
|
||||
void requestFinished(QNetworkReply *reply);
|
||||
|
||||
private:
|
||||
|
||||
enum Status {
|
||||
WaitingHttp = 0,
|
||||
UsingHttp,
|
||||
FinishedWork
|
||||
};
|
||||
Status status;
|
||||
MTPint128 httpNonce;
|
||||
int32 _flags;
|
||||
|
||||
QNetworkAccessManager manager;
|
||||
QUrl address;
|
||||
|
||||
typedef QSet<QNetworkReply*> Requests;
|
||||
Requests requests;
|
||||
|
||||
};
|
||||
|
||||
class MTProtoConnectionPrivate : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
MTProtoConnectionPrivate(QThread *thread, MTProtoConnection *owner, MTPSessionData *data, uint32 dc);
|
||||
~MTProtoConnectionPrivate();
|
||||
|
||||
void stop();
|
||||
|
||||
int32 getDC() const;
|
||||
|
||||
int32 getState() const;
|
||||
QString transport() const;
|
||||
|
||||
signals:
|
||||
|
||||
void needToReceive();
|
||||
void needToRestart();
|
||||
void stateChanged(qint32 newState);
|
||||
void sessionResetDone();
|
||||
|
||||
void needToSendAsync();
|
||||
void sendAnythingAsync(quint64 msWait);
|
||||
void sendHttpWaitAsync();
|
||||
void sendPongAsync(quint64 msgId, quint64 pingId);
|
||||
void sendMsgsStateInfoAsync(quint64 msgId, QByteArray data);
|
||||
void resendAsync(quint64 msgId, quint64 msCanWait, bool forceContainer, bool sendMsgStateInfo);
|
||||
void resendManyAsync(QVector<quint64> msgIds, quint64 msCanWait, bool forceContainer, bool sendMsgStateInfo);
|
||||
void resendAllAsync();
|
||||
|
||||
void finished(MTProtoConnection *connection);
|
||||
|
||||
public slots:
|
||||
|
||||
void retryByTimer();
|
||||
void restartNow();
|
||||
void restart(bool mayBeBadKey = false);
|
||||
|
||||
void onPingSender();
|
||||
void onPingSendForce();
|
||||
|
||||
void onWaitConnectedFailed();
|
||||
void onWaitReceivedFailed();
|
||||
void onWaitIPv4Failed();
|
||||
|
||||
void onOldConnection();
|
||||
void onSentSome(uint64 size);
|
||||
void onReceivedSome();
|
||||
|
||||
void onReadyData();
|
||||
void socketStart(bool afterConfig = false);
|
||||
|
||||
void onConnected4();
|
||||
void onConnected6();
|
||||
void onDisconnected4();
|
||||
void onDisconnected6();
|
||||
void onError4(bool mayBeBadKey = false);
|
||||
void onError6(bool mayBeBadKey = false);
|
||||
|
||||
void doFinish();
|
||||
|
||||
// Auth key creation packet receive slots
|
||||
void pqAnswered();
|
||||
void dhParamsAnswered();
|
||||
void dhClientParamsAnswered();
|
||||
|
||||
// General packet receive slot, connected to conn->receivedData signal
|
||||
void handleReceived();
|
||||
|
||||
// Sessions signals, when we need to send something
|
||||
void tryToSend();
|
||||
|
||||
void updateAuthKey();
|
||||
|
||||
void onConfigLoaded();
|
||||
|
||||
private:
|
||||
|
||||
void doDisconnect();
|
||||
|
||||
void createConn(bool createIPv4, bool createIPv6);
|
||||
void destroyConn(MTPabstractConnection **conn = 0); // 0 - destory all
|
||||
|
||||
mtpMsgId placeToContainer(mtpRequest &toSendRequest, mtpMsgId &bigMsgId, mtpMsgId *&haveSentArr, mtpRequest &req);
|
||||
mtpMsgId prepareToSend(mtpRequest &request, mtpMsgId currentLastId);
|
||||
mtpMsgId replaceMsgId(mtpRequest &request, mtpMsgId newId);
|
||||
|
||||
bool sendRequest(mtpRequest &request, bool needAnyResponse, QReadLocker &lockFinished);
|
||||
mtpRequestId wasSent(mtpMsgId msgId) const;
|
||||
|
||||
int32 handleOneReceived(const mtpPrime *from, const mtpPrime *end, uint64 msgId, int32 serverTime, uint64 serverSalt, bool badTime);
|
||||
mtpBuffer ungzip(const mtpPrime *from, const mtpPrime *end) const;
|
||||
void handleMsgsStates(const QVector<MTPlong> &ids, const string &states, QVector<MTPlong> &acked);
|
||||
|
||||
void clearMessages();
|
||||
|
||||
bool setState(int32 state, int32 ifState = MTProtoConnection::UpdateAlways);
|
||||
mutable QReadWriteLock stateConnMutex;
|
||||
int32 _state;
|
||||
|
||||
bool _needSessionReset;
|
||||
void resetSession();
|
||||
|
||||
uint32 dc;
|
||||
MTProtoConnection *_owner;
|
||||
MTPabstractConnection *_conn, *_conn4, *_conn6;
|
||||
|
||||
SingleTimer retryTimer; // exp retry timer
|
||||
uint32 retryTimeout;
|
||||
quint64 retryWillFinish;
|
||||
|
||||
SingleTimer oldConnectionTimer;
|
||||
bool oldConnection;
|
||||
|
||||
SingleTimer _waitForConnectedTimer, _waitForReceivedTimer, _waitForIPv4Timer;
|
||||
uint32 _waitForReceived, _waitForConnected;
|
||||
int64 firstSentAt;
|
||||
|
||||
QVector<MTPlong> ackRequestData, resendRequestData;
|
||||
|
||||
// if badTime received - search for ids in sessionData->haveSent and sessionData->wereAcked and sync time/salt, return true if found
|
||||
bool requestsFixTimeSalt(const QVector<MTPlong> &ids, int32 serverTime, uint64 serverSalt);
|
||||
|
||||
// remove msgs with such ids from sessionData->haveSent, add to sessionData->wereAcked
|
||||
void requestsAcked(const QVector<MTPlong> &ids, bool byResponse = false);
|
||||
|
||||
mtpPingId _pingId, _pingIdToSend;
|
||||
uint64 _pingSendAt;
|
||||
mtpMsgId _pingMsgId;
|
||||
SingleTimer _pingSender;
|
||||
|
||||
void resend(quint64 msgId, quint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false);
|
||||
void resendMany(QVector<quint64> msgIds, quint64 msCanWait = 0, bool forceContainer = false, bool sendMsgStateInfo = false);
|
||||
|
||||
template <typename TRequest>
|
||||
void sendRequestNotSecure(const TRequest &request);
|
||||
|
||||
template <typename TResponse>
|
||||
bool readResponseNotSecure(TResponse &response);
|
||||
|
||||
bool restarted, _finished;
|
||||
|
||||
uint64 keyId;
|
||||
QReadWriteLock sessionDataMutex;
|
||||
MTPSessionData *sessionData;
|
||||
bool myKeyLock;
|
||||
void lockKey();
|
||||
void unlockKey();
|
||||
|
||||
// Auth key creation fields and methods
|
||||
struct AuthKeyCreateData {
|
||||
AuthKeyCreateData()
|
||||
: new_nonce(*(MTPint256*)((uchar*)new_nonce_buf))
|
||||
, auth_key_aux_hash(*(MTPlong*)((uchar*)new_nonce_buf + 33))
|
||||
, retries(0)
|
||||
, g(0)
|
||||
, req_num(0)
|
||||
, msgs_sent(0) {
|
||||
memset(new_nonce_buf, 0, sizeof(new_nonce_buf));
|
||||
memset(aesKey, 0, sizeof(aesKey));
|
||||
memset(aesIV, 0, sizeof(aesIV));
|
||||
memset(auth_key, 0, sizeof(auth_key));
|
||||
}
|
||||
MTPint128 nonce, server_nonce;
|
||||
uchar new_nonce_buf[41]; // 32 bytes new_nonce + 1 check byte + 8 bytes of auth_key_aux_hash
|
||||
MTPint256 &new_nonce;
|
||||
MTPlong &auth_key_aux_hash;
|
||||
|
||||
uint32 retries;
|
||||
MTPlong retry_id;
|
||||
|
||||
int32 g;
|
||||
|
||||
uchar aesKey[32], aesIV[32];
|
||||
uint32 auth_key[64];
|
||||
MTPlong auth_key_hash;
|
||||
|
||||
uint32 req_num; // sent not encrypted request number
|
||||
uint32 msgs_sent;
|
||||
};
|
||||
struct AuthKeyCreateStrings {
|
||||
QByteArray dh_prime;
|
||||
QByteArray g_a;
|
||||
};
|
||||
AuthKeyCreateData *authKeyData;
|
||||
AuthKeyCreateStrings *authKeyStrings;
|
||||
|
||||
void dhClientParamsSend();
|
||||
void authKeyCreated();
|
||||
void clearAuthKeyData();
|
||||
|
||||
};
|
|
@ -1,85 +0,0 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
class mtpPublicRSA {
|
||||
public:
|
||||
mtpPublicRSA(const char *key) : data(new mtpPublicRSAInner(PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(key), -1), 0, 0, 0), 0)) {
|
||||
if (!data->prsa) return;
|
||||
|
||||
int32 nBytes = BN_num_bytes(data->prsa->n);
|
||||
int32 eBytes = BN_num_bytes(data->prsa->e);
|
||||
string nStr(nBytes, 0), eStr(eBytes, 0);
|
||||
BN_bn2bin(data->prsa->n, (uchar*)&nStr[0]);
|
||||
BN_bn2bin(data->prsa->e, (uchar*)&eStr[0]);
|
||||
|
||||
mtpBuffer tmp;
|
||||
MTP_string(nStr).write(tmp);
|
||||
MTP_string(eStr).write(tmp);
|
||||
|
||||
uchar sha1Buffer[20];
|
||||
data->fp = *(uint64*)(hashSha1(&tmp[0], tmp.size() * sizeof(mtpPrime), sha1Buffer) + 3);
|
||||
}
|
||||
|
||||
mtpPublicRSA(const mtpPublicRSA &v) : data(v.data) {
|
||||
++data->cnt;
|
||||
}
|
||||
|
||||
mtpPublicRSA &operator=(const mtpPublicRSA &v) {
|
||||
if (data != v.data) {
|
||||
destroy();
|
||||
data = v.data;
|
||||
++data->cnt;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint64 fingerPrint() const {
|
||||
return data->fp;
|
||||
}
|
||||
|
||||
RSA *key() {
|
||||
return data->prsa;
|
||||
}
|
||||
|
||||
~mtpPublicRSA() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
private:
|
||||
void destroy() {
|
||||
if (!--data->cnt) {
|
||||
delete data;
|
||||
}
|
||||
}
|
||||
|
||||
struct mtpPublicRSAInner {
|
||||
mtpPublicRSAInner(RSA *_prsa, uint64 _fp) : prsa(_prsa), cnt(1), fp(_fp) {
|
||||
}
|
||||
~mtpPublicRSAInner() {
|
||||
RSA_free(prsa);
|
||||
}
|
||||
RSA *prsa;
|
||||
uint32 cnt;
|
||||
uint64 fp;
|
||||
};
|
||||
mtpPublicRSAInner *data;
|
||||
};
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-2016 John Preston, https://desktop.telegram.org
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
template <typename TRequest>
|
||||
mtpRequestId MTProtoSession::send(const TRequest &request, RPCResponseHandler callbacks, uint64 msCanWait, bool needsLayer, bool toMainDC, mtpRequestId after) {
|
||||
mtpRequestId requestId = 0;
|
||||
try {
|
||||
uint32 requestSize = request.innerLength() >> 2;
|
||||
mtpRequest reqSerialized(mtpRequestData::prepare(requestSize));
|
||||
request.write(*reqSerialized);
|
||||
|
||||
DEBUG_LOG(("MTP Info: adding request to toSendMap, msCanWait %1").arg(msCanWait));
|
||||
|
||||
reqSerialized->msDate = getms(true); // > 0 - can send without container
|
||||
reqSerialized->needsLayer = needsLayer;
|
||||
if (after) reqSerialized->after = _mtp_internal::getRequest(after);
|
||||
requestId = _mtp_internal::storeRequest(reqSerialized, callbacks);
|
||||
|
||||
sendPrepared(reqSerialized, msCanWait);
|
||||
} catch (Exception &e) {
|
||||
requestId = 0;
|
||||
_mtp_internal::rpcErrorOccured(requestId, callbacks, rpcClientError("NO_REQUEST_ID", QString("send() failed to queue request, exception: %1").arg(e.what())));
|
||||
}
|
||||
if (requestId) _mtp_internal::registerRequest(requestId, toMainDC ? -getDcWithShift() : getDcWithShift());
|
||||
return requestId;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue