Merge branch 'dev'

This commit is contained in:
John Preston 2016-03-26 11:52:14 +03:00
commit 9378c05ef1
144 changed files with 18763 additions and 11915 deletions

1
.gitignore vendored
View file

@ -10,6 +10,7 @@
*.suo
*.sdf
*.opensdf
*.opendb
/Telegram/*.aps
/Win32/
ipch/

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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() {
}

View file

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

View file

@ -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() {
}

View file

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

View file

@ -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() {
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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?";

View file

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

View file

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

View file

@ -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" = "삭제";

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View 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);

View file

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

View file

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

View file

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

View file

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

View file

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

View 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

View file

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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View file

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

View file

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

View file

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

View file

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

View 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

View 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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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