Compare commits

...

33 Commits

Author SHA1 Message Date
John Preston
f619afc4c6 Alpha 1.0.33: Fix build for Xcode. 2017-04-16 00:45:25 +03:00
John Preston
7ad7028880 Alpha 1.0.33: Various fixes and improvements. 2017-04-16 00:36:46 +03:00
John Preston
8e241e8b78 Improve Emoji-Stickers-GIFs panel.
Fix broken minimal panel size.
Remove HistoryWidget context menu from the panel.
2017-04-16 00:05:57 +03:00
John Preston
6226cf2809 Display video message playback progress time. 2017-04-15 23:30:28 +03:00
John Preston
7312114b75 Use full volume for video messages.
Set video volume to VideoVolume in MediaView and to 1 in HistoryGif.
2017-04-15 22:51:53 +03:00
John Preston
0ff299758a Use correct lang phrases for video messages. 2017-04-15 22:51:44 +03:00
John Preston
de7c886008 Move passcode management from MainWindow.
Check for auto lock in AuthSession. Don't autolock while video plays.
Closes #3219
2017-04-15 21:51:12 +03:00
John Preston
e3aacc8072 Don't change notification sound to empty on mute.
If notifications are muted no one from official apps play sound.
So there is no need to turn the sound off, otherwise if you enable
the notifications from a different app the sound stays off.

Enable the sound (change to "default") when turning notifications on.
2017-04-15 21:51:11 +03:00
John Preston
4fc2c18f58 Fix crash in intro label crossfade animation.
Remove the assertion because the assumption was not valid.

Closed #3270
2017-04-15 20:40:23 +03:00
John Preston
8a8e101cd0 Fix crash in file downloader destruction.
Regression was introduced in 835b1801bc.

We need to destroy all FileLoader instances before destroying the
Downloader instance, because they hold pointers to it and call its
methods in destructor if they need to cancel some MTP requests.
2017-04-15 20:39:32 +03:00
John Preston
c10dee11e8 Fix crash in macOS notifications manager init.
Regression was introduced in 1725927aea.

Also create the thread only when it is first needed.
2017-04-15 20:39:25 +03:00
John Preston
4e5c4a462b Fix crash in videoplayer audio stream loader.
Regression was introduced in 835b1801bc.
2017-04-15 20:32:14 +03:00
John Preston
bc7139d67a Fix crash in AuthSession::Exists() if there is no Messenger.
Also use toggleAnimated instead of show/hideAnimated in intro.
2017-04-15 19:28:07 +03:00
Ariel Jannai
bf0bf908c2 Update building-msvc.md (#3264)
* Update building-msvc.md

Fixed some wrong, missing or unclear parts. Based on the problems I came into when trying to build and configure everything.

Ninja not in path - from #3237
Newer msys - from #3247
Telegram.sln - from #2811

* Updated from the PR comments and added a signature


Signed-off-by: Ariel Jannai <arieljannai@gmail.com> (github: arieljannai)

* Updated by the latest comments from the PR

Signed-off-by: Ariel Jannai <arieljannai@gmail.com> (github: arieljannai)
2017-04-14 11:58:10 +02:00
John Preston
dd005d9027 Generate correct lang tag count, not hardcoded. 2017-04-13 11:51:47 +03:00
John Preston
670a725c53 Alpha 1.0.32: Fix round video checks in MediaView. 2017-04-12 23:34:57 +03:00
John Preston
af28e3b0d7 Alpha 1.0.32: Fix build in Xcode. 2017-04-12 23:28:04 +03:00
John Preston
ca90b8b8fd Alpha 1.0.32: Test the new API and CDN support. 2017-04-12 23:04:34 +03:00
John Preston
7b7b9db20b Add support for video messages send actions.
Animate record and upload the same way as voice messages.
2017-04-12 22:37:47 +03:00
John Preston
96dbb38aaa Improve mute and info display in video messages.
Display mute on top of the video and move info to the right side
for incoming messages (because of channel authors and views count).
2017-04-12 22:37:28 +03:00
John Preston
1b7777e3a0 Pause other GIFs when playing round video message. 2017-04-12 22:37:28 +03:00
John Preston
5480a63beb Support round video inline playback with sound. 2017-04-12 22:37:27 +03:00
John Preston
cbf040b4dc Crop round video messages to a circle.
Also display unread media dot like in voice messages.
2017-04-12 22:37:26 +03:00
John Preston
8eb7f1f1aa Display round video messages using HistoryGif.
Use autodownload and autoplay options from GIFs.
Also improve EditCaptionBox code.
2017-04-12 22:37:26 +03:00
John Preston
8d28d0691f API scheme updated to layer 66.
Support CDN file download.
2017-04-12 22:36:25 +03:00
John Preston
7dd24a30b5 Replace MetaLang with codegen_lang. 2017-04-12 22:18:42 +03:00
John Preston
1725927aea Clear macOS notifications in a separate thread.
Sometimes NSUserNotificationCenter -deliveredNotifications method call
freezes for a long time, so now we use it only in a separate thread and
we group all the requests for clearing while another clearing is done.
2017-04-12 15:50:35 +03:00
John Preston
50ea4e316e Improve macOS window behavior.
Don't deactivate the application when the main window is hidden.
Such behavior provides some unwanted windows reordering in the
current workspace when the window is hidden by Cmd+W.

Ignore app activation by applicationDidBecomeActive: notification
for a short period of time after a user notification for other app
instance was received (the system sends them sometimes and the main
window is shown + activated for a wrong instance of the application).
2017-04-12 15:50:12 +03:00
srazi
734b426518 Fix canceling forward when clicking on _userpicButton or _cloudButton (#3248)
- Fixes #3192

Signed-off-by: srazi <s.r.alavizadeh@gmail.com> (github: srazi)
2017-04-11 23:03:34 +03:00
Nicholas Guriev
232d3dcb54 Make theme preview more realistic (#3250)
Signed-off-by: Nicholas Guriev <guriev-ns@ya.ru> (github: mymedia2)
2017-04-11 21:16:12 +03:00
John Preston
eaf1e2b18e Alpha 1.0.31: Fix layout for message edit. 2017-04-11 21:04:28 +03:00
John Preston
4b7e5750ec Alpha 1.0.31: Fix crash in localstorage. 2017-04-11 20:31:20 +03:00
John Preston
d4af14041c Fix build for OS X 10.6-10.7.
Qt 5.3.2 doesn't support QTimer::singleShot(delay, lambda).
2017-04-11 18:44:11 +03:00
116 changed files with 3088 additions and 1970 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

View File

@@ -657,6 +657,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_action_pinned_media_video" = "a video";
"lng_action_pinned_media_audio" = "an audio file";
"lng_action_pinned_media_voice" = "a voice message";
"lng_action_pinned_media_video_message" = "a video message";
"lng_action_pinned_media_file" = "a file";
"lng_action_pinned_media_gif" = "a GIF animation";
"lng_action_pinned_media_contact" = "a contact information";
@@ -810,6 +811,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"lng_in_dlg_audio_file" = "Audio file";
"lng_in_dlg_contact" = "Contact";
"lng_in_dlg_audio" = "Voice message";
"lng_in_dlg_video_message" = "Video message";
"lng_in_dlg_file" = "File";
"lng_in_dlg_sticker" = "Sticker";
"lng_in_dlg_sticker_emoji" = "{emoji} Sticker";
@@ -889,6 +891,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
"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_record_round" = "recording a video message";
"lng_user_action_record_round" = "{user} is recording a video message";
"lng_send_action_upload_round" = "sending a video message";
"lng_user_action_upload_round" = "{user} is sending a video 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";

View File

@@ -201,7 +201,7 @@ fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileL
fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation;
userEmpty#200250ba id:int = User;
user#d10d979a flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?string bot_inline_placeholder:flags.19?string = User;
user#2e13f4c3 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?string bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
userProfilePhoto#d559d8c8 photo_id:long photo_small:FileLocation photo_big:FileLocation = UserProfilePhoto;
@@ -357,6 +357,8 @@ inputMessagesFilterVoice#50f5c392 = MessagesFilter;
inputMessagesFilterMusic#3751b49e = MessagesFilter;
inputMessagesFilterChatPhotos#3a20ecb8 = MessagesFilter;
inputMessagesFilterPhoneCalls#80c99768 flags:# missed:flags.0?true = MessagesFilter;
inputMessagesFilterRoundVoice#7a7c17a4 = MessagesFilter;
inputMessagesFilterRoundVideo#b549da53 = MessagesFilter;
updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update;
updateMessageID#4e90bfd6 id:int random_id:long = Update;
@@ -440,8 +442,9 @@ photos.photosSlice#15051f54 count:int photos:Vector<Photo> users:Vector<User> =
photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
upload.fileCdnRedirect#1508485a dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes = upload.File;
dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true id:int ip_address:string port:int = DcOption;
dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true id:int ip_address:string port:int = DcOption;
config#cb601684 flags:# phonecalls_enabled:flags.1?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int chat_big_size:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int rating_e_decay:int stickers_recent_limit:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string disabled_features:Vector<DisabledFeature> = Config;
@@ -501,6 +504,8 @@ sendMessageUploadDocumentAction#aa0cd9e4 progress:int = SendMessageAction;
sendMessageGeoLocationAction#176f8ba1 = SendMessageAction;
sendMessageChooseContactAction#628cbc6f = SendMessageAction;
sendMessageGamePlayAction#dd6a8f48 = SendMessageAction;
sendMessageRecordRoundAction#88f27fbc = SendMessageAction;
sendMessageUploadRoundAction#bb718624 = SendMessageAction;
contacts.found#1aa1f784 results:Vector<Peer> chats:Vector<Chat> users:Vector<User> = contacts.Found;
@@ -533,7 +538,7 @@ accountDaysTTL#b8d0afdf days:int = AccountDaysTTL;
documentAttributeImageSize#6c37c15c w:int h:int = DocumentAttribute;
documentAttributeAnimated#11b58939 = DocumentAttribute;
documentAttributeSticker#6319d612 flags:# mask:flags.1?true alt:string stickerset:InputStickerSet mask_coords:flags.0?MaskCoords = DocumentAttribute;
documentAttributeVideo#5910cccb duration:int w:int h:int = DocumentAttribute;
documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true duration:int w:int h:int = DocumentAttribute;
documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute;
documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
@@ -853,6 +858,13 @@ phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?tr
phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector<User> = phone.PhoneCall;
upload.cdnFileReuploadNeeded#eea8e46e request_token:bytes = upload.CdnFile;
upload.cdnFile#a99fca4f bytes:bytes = upload.CdnFile;
cdnPublicKey#c982eaba dc_id:int public_key:string = CdnPublicKey;
cdnConfig#5725e40a public_keys:Vector<CdnPublicKey> = CdnConfig;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1024,6 +1036,8 @@ upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool;
upload.getFile#e3a6cfb5 location:InputFileLocation offset:int limit:int = upload.File;
upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool;
upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile;
upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile;
upload.reuploadCdnFile#2e7a2020 file_token:bytes request_token:bytes = Bool;
help.getConfig#c4f9186b = Config;
help.getNearestDc#1fb33026 = NearestDc;
@@ -1034,6 +1048,7 @@ help.getSupport#9cdf08cd = help.Support;
help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
help.getTermsOfService#350170f3 = help.TermsOfService;
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
help.getCdnConfig#52029342 = CdnConfig;
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
@@ -1082,4 +1097,4 @@ phone.discardCall#78d413a6 peer:InputPhoneCall duration:int reason:PhoneCallDisc
phone.setCallRating#1c536a34 peer:InputPhoneCall rating:int comment:string = Updates;
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
// LAYER 65
// LAYER 66

View File

@@ -9,7 +9,7 @@
<Identity Name="TelegramMessengerLLP.TelegramDesktop"
ProcessorArchitecture="ARCHITECTURE"
Publisher="CN=536BC709-8EE1-4478-AF22-F0F0F26FF64A"
Version="1.0.30.0" />
Version="1.0.33.0" />
<Properties>
<DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,30,0
PRODUCTVERSION 1,0,30,0
FILEVERSION 1,0,33,0
PRODUCTVERSION 1,0,33,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -52,10 +52,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop"
VALUE "FileVersion", "1.0.30.0"
VALUE "FileVersion", "1.0.33.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.0.30.0"
VALUE "ProductVersion", "1.0.33.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,30,0
PRODUCTVERSION 1,0,30,0
FILEVERSION 1,0,33,0
PRODUCTVERSION 1,0,33,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -43,10 +43,10 @@ BEGIN
BEGIN
VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "1.0.30.0"
VALUE "FileVersion", "1.0.33.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.0.30.0"
VALUE "ProductVersion", "1.0.33.0"
END
END
BLOCK "VarFileInfo"

View File

@@ -1,795 +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-2017 John Preston, https://desktop.telegram.org
*/
#include "genlang.h"
#include <QtCore/QtPlugin>
#ifdef Q_OS_WIN
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin)
#endif
#ifdef Q_OS_MAC
//Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin)
#endif
typedef unsigned int uint32;
QString layoutDirection;
typedef QMap<QByteArray, QString> LangKeys;
LangKeys keys;
typedef QMap<QByteArray, ushort> LangTags;
LangTags tags;
typedef QMap<QByteArray, QVector<QByteArray> > LangKeysTags;
LangKeysTags keysTags;
typedef QVector<QByteArray> KeysOrder;
KeysOrder keysOrder;
KeysOrder tagsOrder;
typedef QMap<QByteArray, QMap<QByteArray, QVector<QString> > > LangKeysCounted;
LangKeysCounted keysCounted;
static const QChar TextCommand(0x0010);
static const QChar TextCommandLangTag(0x0020);
bool skipWhitespaces(const char *&from, const char *end) {
while (from < end && (*from == ' ' || *from == '\n' || *from == '\t' || *from == '\r')) {
++from;
}
return (from < end);
}
bool skipComment(const char *&from, const char *end) {
if (from >= end) return false;
if (*from == '/') {
if (from + 1 >= end) return true;
if (*(from + 1) == '*') {
from += 2;
while (from + 1 < end && (*from != '*' || *(from + 1) != '/')) {
++from;
}
from += 2;
return (from < end);
} else if (*(from + 1) == '/') {
from += 2;
while (from < end && *from != '\n' && *from != '\r') {
++from;
}
if (from < end) ++from;
return true;
} else {
return true;
}
}
return true;
}
bool skipJunk(const char *&from, const char *end) {
const char *start;
do {
start = from;
if (!skipWhitespaces(from, end)) return false;
if (!skipComment(from, end)) throw Exception("Unexpected end of comment!");
} while (start != from);
return true;
}
inline bool _lngEquals(const QByteArray &key, int from, int len, const char *value, int size) {
if (size != len || from + len > key.size()) return false;
for (const char *v = key.constData() + from, *e = v + len; v != e; ++v, ++value) {
if (*v != *value) return false;
}
return true;
}
#define LNG_EQUALS_PART(key, from, len, value) _lngEquals(key, from, len, value, sizeof(value) - 1)
#define LNG_EQUALS_TAIL(key, from, value) _lngEquals(key, from, key.size() - from, value, sizeof(value) - 1)
#define LNG_EQUALS(key, value) _lngEquals(key, 0, key.size(), value, sizeof(value) - 1)
static const int MaxCountedValues = 6;
void readKeyValue(const char *&from, const char *end) {
if (!skipJunk(from, end)) return;
if (*from != '"') throw Exception(QString("Expected quote before key name!"));
const char *nameStart = ++from;
while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) {
++from;
}
if (from == nameStart) throw Exception(QString("Expected key name!"));
QByteArray varName = QByteArray(nameStart, int(from - nameStart));
for (const char *t = nameStart; t + 1 < from; ++t) {
if (*t == '_') {
if (*(t + 1) == '_') throw Exception(QString("Bad key name: %1").arg(QLatin1String(varName)));
++t;
}
}
if (from == end || *from != '"') throw Exception(QString("Expected quote after key name in key '%1'!").arg(QLatin1String(varName)));
++from;
if (!skipJunk(from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (*from != '=') throw Exception(QString("'=' expected in key '%1'!").arg(QLatin1String(varName)));
if (!skipJunk(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (*from != '"') throw Exception(QString("Expected string after '=' in key '%1'!").arg(QLatin1String(varName)));
QByteArray varValue;
const char *start = ++from;
QVector<QByteArray> tagsList;
while (from < end && *from != '"') {
if (*from == '\n') {
throw Exception(QString("Unexpected end of string in key '%1'!").arg(QLatin1String(varName)));
}
if (*from == '\\') {
if (from + 1 >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{') {
if (from > start) varValue.append(start, int(from - start));
start = ++from;
} else if (*(from + 1) == 'n') {
if (from > start) varValue.append(start, int(from - start));
varValue.append('\n');
start = (++from) + 1;
}
} else if (*from == '{') {
if (from > start) varValue.append(start, int(from - start));
const char *tagStart = ++from;
while (from < end && ((*from >= 'a' && *from <= 'z') || (*from >= 'A' && *from <= 'Z') || *from == '_' || (*from >= '0' && *from <= '9'))) {
++from;
}
if (from == tagStart) throw Exception(QString("Expected tag name in key '%1'!").arg(QLatin1String(varName)));
QByteArray tagName = QByteArray(tagStart, int(from - tagStart));
if (from == end || (*from != '}' && *from != ':')) throw Exception(QString("Expected '}' or ':' after tag name in key '%1'!").arg(QLatin1String(varName)));
LangTags::const_iterator i = tags.constFind(tagName);
if (i == tags.cend()) {
i = tags.insert(tagName, tagsOrder.size());
tagsOrder.push_back(tagName);
}
if (0x0020 + *i > 0x007F) throw Exception(QString("Too many different tags in key '%1'").arg(QLatin1String(varName)));
QString tagReplacer(4, TextCommand);
tagReplacer[1] = TextCommandLangTag;
tagReplacer[2] = QChar(0x0020 + *i);
varValue.append(tagReplacer.toUtf8());
for (int j = 0, s = tagsList.size(); j < s; ++j) {
if (tagsList.at(j) == tagName) throw Exception(QString("Tag '%1' double used in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
}
tagsList.push_back(tagName);
if (*from == ':') {
start = ++from;
QVector<QString> &counted(keysCounted[varName][tagName]);
QByteArray subvarValue;
bool foundtag = false;
while (from < end && *from != '"' && *from != '}') {
if (*from == '|') {
if (from > start) subvarValue.append(start, int(from - start));
counted.push_back(QString::fromUtf8(subvarValue));
subvarValue = QByteArray();
foundtag = false;
start = from + 1;
}
if (*from == '\n') {
throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
}
if (*from == '\\') {
if (from + 1 >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{' || *(from + 1) == '#') {
if (from > start) subvarValue.append(start, int(from - start));
start = ++from;
} else if (*(from + 1) == 'n') {
if (from > start) subvarValue.append(start, int(from - start));
subvarValue.append('\n');
start = (++from) + 1;
}
} else if (*from == '{') {
throw Exception(QString("Unexpected tag inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
} else if (*from == '#') {
if (foundtag) throw Exception(QString("Replacement '#' double used inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
foundtag = true;
if (from > start) subvarValue.append(start, int(from - start));
subvarValue.append(tagReplacer.toUtf8());
start = from + 1;
}
++from;
}
if (from >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (*from == '"') throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (from > start) subvarValue.append(start, int(from - start));
counted.push_back(QString::fromUtf8(subvarValue));
if (counted.size() > MaxCountedValues) {
throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
}
}
start = from + 1;
}
++from;
}
if (from >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (from > start) varValue.append(start, int(from - start));
if (!skipJunk(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (*from != ';') throw Exception(QString("';' expected after \"value\" in key '%1'!").arg(QLatin1String(varName)));
skipJunk(++from, end);
if (varName == "direction") {
throw Exception(QString("Unexpected value for 'direction' in key '%1'!").arg(QLatin1String(varName)));
} else if (!LNG_EQUALS_PART(varName, 0, 4, "lng_")) {
throw Exception(QString("Bad key '%1'!").arg(QLatin1String(varName)));
} else if (keys.constFind(varName) != keys.cend()) {
throw Exception(QString("Key '%1' doubled!").arg(QLatin1String(varName)));
} else {
keys.insert(varName, QString::fromUtf8(varValue));
keysTags.insert(varName, tagsList);
keysOrder.push_back(varName);
}
}
QString escapeCpp(const QByteArray &key, QString value) {
if (value.isEmpty()) return "QString()";
QString res;
res.reserve(value.size() * 10);
bool instr = false;
for (const QChar *ch = value.constData(), *e = value.constData() + value.size(); ch != e; ++ch) {
if (ch->unicode() > 0x007F) {
if (instr) {
res.append('"');
instr = false;
}
res.append(' ').append('u').append('"').append('\\').append('x').append(QString("%1").arg(ch->unicode(), 4, 16, QChar('0'))).append('"');
} else {
if (ch->unicode() == '\\' || ch->unicode() == '\n' || ch->unicode() == '\r' || ch->unicode() == '"') {
if (!instr) {
res.append(' ').append('u').append('"');
instr = true;
}
res.append('\\');
if (ch->unicode() == '\\' || ch->unicode() == '"') {
res.append(*ch);
} else if (ch->unicode() == '\n') {
res.append('n');
} else if (ch->unicode() == '\r') {
res.append('r');
}
} else if (ch->unicode() < 0x0020) {
if (*ch == TextCommand) {
if (ch + 3 >= e || (ch + 1)->unicode() != TextCommandLangTag || (ch + 2)->unicode() > 0x007F || (ch + 2)->unicode() < 0x0020 || *(ch + 3) != TextCommand) {
throw Exception(QString("Bad value for key '%1'").arg(QLatin1String(key)));
} else {
if (instr) {
res.append('"');
instr = false;
}
res.append(' ').append('u').append('"');
res.append('\\').append('x').append(QString("%1").arg(ch->unicode(), 2, 16, QChar('0')));
res.append('\\').append('x').append(QString("%1").arg((ch + 1)->unicode(), 2, 16, QChar('0')));
res.append('\\').append('x').append(QString("%1").arg((ch + 2)->unicode(), 2, 16, QChar('0')));
res.append('\\').append('x').append(QString("%1").arg((ch + 3)->unicode(), 2, 16, QChar('0')));
res.append('"');
ch += 3;
}
} else {
throw Exception(QString("Bad value for key '%1'").arg(QLatin1String(key)));
}
} else {
if (!instr) {
res.append(' ').append('u').append('"');
instr = true;
}
res.append(*ch);
}
}
}
if (instr) res.append('"');
return "qsl(" + res.mid(1) + ")";
}
void writeCppKey(QTextStream &tcpp, const QByteArray &key, const QString &val) {
tcpp << "\t\t\tset(" << key << ", " << escapeCpp(key, val) << ");\n";
}
bool genLang(const QString &lang_in, const QString &lang_out) {
QString lang_cpp = lang_out + ".cpp", lang_h = lang_out + ".h";
QFile f(lang_in);
if (!f.open(QIODevice::ReadOnly)) {
cout << "Could not open lang input file '" << lang_in.toUtf8().constData() << "'!\n";
QCoreApplication::exit(1);
return false;
}
QByteArray checkCodec = f.read(3);
if (checkCodec.size() < 3) {
cout << "Bad lang input file '" << lang_in.toUtf8().constData() << "'!\n";
QCoreApplication::exit(1);
return false;
}
f.seek(0);
QByteArray data;
int skip = 0;
if ((checkCodec.at(0) == '\xFF' && checkCodec.at(1) == '\xFE') || (checkCodec.at(0) == '\xFE' && checkCodec.at(1) == '\xFF') || (checkCodec.at(1) == 0)) {
QTextStream stream(&f);
stream.setCodec("UTF-16");
QString string = stream.readAll();
if (stream.status() != QTextStream::Ok) {
cout << "Could not read valid UTF-16 file '" << lang_in.toUtf8().constData() << "'!\n";
QCoreApplication::exit(1);
return false;
}
f.close();
data = string.toUtf8();
} else if (checkCodec.at(0) == 0) {
QByteArray tmp = "\xFE\xFF" + f.readAll(); // add fake UTF-16 BOM
f.close();
QTextStream stream(&tmp);
stream.setCodec("UTF-16");
QString string = stream.readAll();
if (stream.status() != QTextStream::Ok) {
cout << "Could not read valid UTF-16 file '" << lang_in.toUtf8().constData() << "'!\n";
QCoreApplication::exit(1);
return false;
}
data = string.toUtf8();
} else {
data = f.readAll();
if (checkCodec.at(0) == '\xEF' && checkCodec.at(1) == '\xBB' && checkCodec.at(2) == '\xBF') {
skip = 3; // skip UTF-8 BOM
}
}
const char *text = data.constData() + skip, *end = text + data.size() - skip;
try {
while (text < end) {
readKeyValue(text, end);
}
QByteArray cppText, hText;
{
QTextStream tcpp(&cppText), th(&hText);
tcpp.setCodec("ISO 8859-1");
th.setCodec("ISO 8859-1");
th << "\
/*\n\
Created from \'/Resources/langs/lang.strings\' by \'/MetaLang\' project\n\
\n\
WARNING! All changes made in this file will be lost!\n\
\n\
This file is part of Telegram Desktop,\n\
the official desktop version of Telegram messaging app, see https://telegram.org\n\
\n\
Telegram Desktop is free software: you can redistribute it and/or modify\n\
it under the terms of the GNU General Public License as published by\n\
the Free Software Foundation, either version 3 of the License, or\n\
(at your option) any later version.\n\
\n\
It is distributed in the hope that it will be useful,\n\
but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\
GNU General Public License for more details.\n\
\n\
In addition, as a special exception, the copyright holders give permission\n\
to link the code of portions of this program with the OpenSSL library.\n\
\n\
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org\n\
*/\n";
th << "#pragma once\n\n";
for (int i = 0, l = tagsOrder.size(); i < l; ++i) {
th << "enum lngtag_" << tagsOrder[i] << " { lt_" << tagsOrder[i] << " = " << i << " };\n";
}
th << "static const ushort lngtags_cnt = " << tagsOrder.size() << ";\n";
th << "static const ushort lngtags_max_counted_values = " << MaxCountedValues << ";\n";
th << "\n";
th << "enum LangKey {\n";
for (int i = 0, l = keysOrder.size(); i < l; ++i) {
if (keysTags[keysOrder[i]].isEmpty()) {
th << "\t" << keysOrder[i] << (i ? "" : " = 0") << ",\n";
} else {
th << "\t" << keysOrder[i] << "__tagged" << (i ? "" : " = 0") << ",\n";
QMap<QByteArray, QVector<QString> > &countedTags(keysCounted[keysOrder[i]]);
if (!countedTags.isEmpty()) {
for (QMap<QByteArray, QVector<QString> >::const_iterator j = countedTags.cbegin(), e = countedTags.cend(); j != e; ++j) {
const auto &counted(*j);
for (int k = 0, s = counted.size(); k < s; ++k) {
th << "\t" << keysOrder[i] << "__" + j.key() + QString::number(k).toUtf8() << ",\n";
}
}
}
}
}
th << "\n\tlngkeys_cnt\n";
th << "};\n\n";
th << "LangString lang(LangKey key);\n\n";
th << "LangString langOriginal(LangKey key);\n\n";
for (int i = 0, l = keysOrder.size(); i < l; ++i) {
QVector<QByteArray> &tagsList(keysTags[keysOrder[i]]);
if (tagsList.isEmpty()) continue;
QMap<QByteArray, QVector<QString> > &countedTags(keysCounted[keysOrder[i]]);
th << "inline LangString " << keysOrder[i] << "(";
for (int j = 0, s = tagsList.size(); j < s; ++j) {
if (countedTags[tagsList[j]].isEmpty()) {
th << "lngtag_" << tagsList[j] << ", const QString &" << tagsList[j] << "__val";
} else {
th << "lngtag_" << tagsList[j] << ", float64 " << tagsList[j] << "__val";
}
if (j + 1 < s) th << ", ";
}
th << ") {\n";
th << "\treturn lang(" << keysOrder[i] << "__tagged)";
for (int j = 0, s = tagsList.size(); j < s; ++j) {
if (countedTags[tagsList[j]].isEmpty()) {
th << ".tag(lt_" << tagsList[j] << ", " << tagsList[j] << "__val)";
} else {
th << ".tag(lt_" << tagsList[j] << ", langCounted(" << keysOrder[i] << "__" << tagsList[j] << "0, lt_" << tagsList[j] << ", " << tagsList[j] << "__val))";
}
}
th << ";\n";
th << "}\n";
}
tcpp << "\
/*\n\
Created from \'/Resources/langs/lang.strings\' by \'/MetaLang\' project\n\
\n\
WARNING! All changes made in this file will be lost!\n\
\n\
This file is part of Telegram Desktop,\n\
the official desktop version of Telegram messaging app, see https://telegram.org\n\
\n\
Telegram Desktop is free software: you can redistribute it and/or modify\n\
it under the terms of the GNU General Public License as published by\n\
the Free Software Foundation, either version 3 of the License, or\n\
(at your option) any later version.\n\
\n\
It is distributed in the hope that it will be useful,\n\
but WITHOUT ANY WARRANTY; without even the implied warranty of\n\
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\
GNU General Public License for more details.\n\
\n\
In addition, as a special exception, the copyright holders give permission\n\
to link the code of portions of this program with the OpenSSL library.\n\
\n\
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE\n\
Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org\n\
*/\n";
tcpp << "#include \"lang.h\"\n\n";
tcpp << "namespace {\n";
tcpp << "\tconst char *_langKeyNames[lngkeys_cnt] = {\n";
for (int i = 0, l = keysOrder.size(); i < l; ++i) {
if (keysTags[keysOrder[i]].isEmpty()) {
tcpp << "\t\t\"" << keysOrder[i] << "\",\n";
} else {
tcpp << "\t\t\"" << keysOrder[i] << "__tagged\",\n";
QMap<QByteArray, QVector<QString> > &countedTags(keysCounted[keysOrder[i]]);
if (!countedTags.isEmpty()) {
for (QMap<QByteArray, QVector<QString> >::const_iterator j = countedTags.cbegin(), e = countedTags.cend(); j != e; ++j) {
const auto &counted(*j);
for (int k = 0, s = counted.size(); k < s; ++k) {
tcpp << "\t\t\"" << keysOrder[i] << "__" + j.key() + QString::number(k).toUtf8() << "\",\n";
}
}
}
}
}
tcpp << "\t};\n\n";
tcpp << "\tLangString _langValues[lngkeys_cnt], _langValuesOriginal[lngkeys_cnt];\n\n";
tcpp << "\tvoid set(LangKey key, const QString &val) {\n";
tcpp << "\t\t_langValues[key] = val;\n";
tcpp << "\t}\n\n";
tcpp << "\tclass LangInit {\n";
tcpp << "\tpublic:\n";
tcpp << "\t\tLangInit() {\n";
for (int i = 0, l = keysOrder.size(); i < l; ++i) {
writeCppKey(tcpp, keysOrder[i] + (keysTags[keysOrder[i]].isEmpty() ? "" : "__tagged"), keys[keysOrder[i]]);
QMap<QByteArray, QVector<QString> > &countedTags(keysCounted[keysOrder[i]]);
if (!countedTags.isEmpty()) {
for (QMap<QByteArray, QVector<QString> >::const_iterator j = countedTags.cbegin(), e = countedTags.cend(); j != e; ++j) {
const auto &counted(*j);
for (int k = 0, s = counted.size(); k < s; ++k) {
writeCppKey(tcpp, keysOrder[i] + "__" + j.key() + QString::number(k).toUtf8(), counted[k]);
}
}
}
}
tcpp << "\t\t}\n";
tcpp << "\t};\n\n";
tcpp << "\tLangInit _langInit;\n\n";
tcpp << "\tinline bool _lngEquals(const QByteArray &key, int from, int len, const char *value, int size) {\n";
tcpp << "\t\tif (size != len || from + len > key.size()) return false;\n";
tcpp << "\t\tfor (const char *v = key.constData() + from, *e = v + len; v != e; ++v, ++value) {\n";
tcpp << "\t\t\tif (*v != *value) return false;\n";
tcpp << "\t\t}\n";
tcpp << "\t\treturn true;\n";
tcpp << "\t}\n";
tcpp << "}\n\n";
tcpp << "#define LNG_EQUALS_PART(key, from, len, value) _lngEquals(key, from, len, value, sizeof(value) - 1)\n";
tcpp << "#define LNG_EQUALS_TAIL(key, from, value) _lngEquals(key, from, key.size() - from, value, sizeof(value) - 1)\n";
tcpp << "#define LNG_EQUALS(key, value) _lngEquals(key, 0, key.size(), value, sizeof(value) - 1)\n\n";
tcpp << "LangString lang(LangKey key) {\n";
tcpp << "\treturn (key < 0 || key > lngkeys_cnt) ? QString() : _langValues[key];\n";
tcpp << "}\n\n";
tcpp << "LangString langOriginal(LangKey key) {\n";
tcpp << "\treturn (key < 0 || key > lngkeys_cnt || _langValuesOriginal[key] == qsl(\"{}\")) ? QString() : (_langValuesOriginal[key].isEmpty() ? _langValues[key] : _langValuesOriginal[key]);\n";
tcpp << "}\n\n";
tcpp << "const char *langKeyName(LangKey key) {\n";
tcpp << "\treturn (key < 0 || key > lngkeys_cnt) ? \"\" : _langKeyNames[key];\n";
tcpp << "}\n\n";
tcpp << "ushort LangLoader::tagIndex(const QByteArray &tag) const {\n";
tcpp << "\tif (tag.isEmpty()) return lngtags_cnt;\n\n";
if (!tags.isEmpty()) {
QString tab("\t");
tcpp << "\tconst char *ch = tag.constData(), *e = tag.constData() + tag.size();\n";
QByteArray current;
int depth = current.size();
tcpp << "\tswitch (*ch) {\n";
for (LangTags::const_iterator i = tags.cbegin(), j = i + 1, e = tags.cend(); i != e; ++i) {
QByteArray tag = i.key();
while (depth > 0 && tag.mid(0, depth) != current) {
tcpp << tab.repeated(depth + 1) << "}\n";
current.chop(1);
--depth;
tcpp << tab.repeated(depth + 1) << "break;\n";
}
do {
if (tag == current) break;
char ich = i.key().at(current.size());
tcpp << tab.repeated(current.size() + 1) << "case '" << ich << "':\n";
if (j == e || ich != ((j.key().size() > depth) ? j.key().at(depth) : 0)) {
if (tag == current + ich) {
tcpp << tab.repeated(depth + 1) << "\tif (ch + " << (depth + 1) << " == e) return lt_" << tag << ";\n";
} else {
tcpp << tab.repeated(depth + 1) << "\tif (LNG_EQUALS_TAIL(tag, " << (depth + 1) << ", \"" << i.key().mid(depth + 1) << "\")) return lt_" << tag << ";\n";
}
tcpp << tab.repeated(depth + 1) << "break;\n";
break;
}
++depth;
current += ich;
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";
}
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;
}
while (QByteArray() != current) {
tcpp << tab.repeated(depth + 1) << "}\n";
current.chop(1);
--depth;
tcpp << tab.repeated(depth + 1) << "break;\n";
}
tcpp << "\t}\n\n";
}
tcpp << "\treturn lngtags_cnt;\n";
tcpp << "}\n\n";
tcpp << "LangKey LangLoader::keyIndex(const QByteArray &key) const {\n";
tcpp << "\tif (key.size() < 5 || !LNG_EQUALS_PART(key, 0, 4, \"lng_\")) return lngkeys_cnt;\n\n";
if (!keys.isEmpty()) {
QString tab("\t");
tcpp << "\tconst char *ch = key.constData(), *e = key.constData() + key.size();\n";
QByteArray current("lng_");
int depth = current.size();
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 (depth > 0 && key.mid(0, depth) != current) {
tcpp << tab.repeated(depth - 3) << "}\n";
current.chop(1);
--depth;
tcpp << tab.repeated(depth - 3) << "break;\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)) {
if (key == current + ich) {
tcpp << tab.repeated(depth - 3) << "\tif (ch + " << (depth + 1) << " == e) return " << key << (keysTags[key].isEmpty() ? "" : "__tagged") << ";\n";
} else {
tcpp << tab.repeated(depth - 3) << "\tif (LNG_EQUALS_TAIL(key, " << (depth + 1) << ", \"" << i.key().mid(depth + 1) << "\")) return " << key << (keysTags[key].isEmpty() ? "" : "__tagged") << ";\n";
}
tcpp << tab.repeated(depth - 3) << "break;\n";
break;
}
++depth;
current += ich;
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";
}
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;
}
while (QByteArray("lng_") != current) {
tcpp << tab.repeated(depth - 3) << "}\n";
current.chop(1);
--depth;
tcpp << tab.repeated(depth - 3) << "break;\n";
}
tcpp << "\t}\n\n";
}
tcpp << "\treturn lngkeys_cnt;\n";
tcpp << "}\n\n";
tcpp << "bool LangLoader::tagReplaced(LangKey key, ushort tag) const {\n";
if (!tags.isEmpty()) {
tcpp << "\tswitch (key) {\n";
for (int i = 0, l = keysOrder.size(); i < l; ++i) {
QVector<QByteArray> &tagsList(keysTags[keysOrder[i]]);
if (tagsList.isEmpty()) continue;
tcpp << "\tcase " << keysOrder[i] << "__tagged: {\n";
tcpp << "\t\tswitch (tag) {\n";
for (int j = 0, s = tagsList.size(); j < s; ++j) {
tcpp << "\t\tcase lt_" << tagsList[j] << ":\n";
}
tcpp << "\t\t\treturn true;\n";
tcpp << "\t\t}\n";
tcpp << "\t} break;\n";
}
tcpp << "\t}\n\n";
}
tcpp << "\treturn false;";
tcpp << "}\n\n";
tcpp << "LangKey LangLoader::subkeyIndex(LangKey key, ushort tag, ushort index) const {\n";
tcpp << "\tif (index >= lngtags_max_counted_values) return lngkeys_cnt;\n\n";
if (!tags.isEmpty()) {
tcpp << "\tswitch (key) {\n";
for (auto key : keysOrder) {
QVector<QByteArray> &tagsList(keysTags[key]);
if (tagsList.isEmpty()) continue;
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 (auto tag : tagsList) {
if (!countedTags[tag].isEmpty()) {
tcpp << "\t\tcase lt_" << tag << ": return LangKey(" << key << "__" << tag << "0 + index);\n";
}
}
tcpp << "\t\t}\n";
tcpp << "\t} break;\n";
}
tcpp << "\t}\n\n";
}
tcpp << "\treturn lngkeys_cnt;\n";
tcpp << "}\n\n";
tcpp << "bool LangLoader::feedKeyValue(LangKey key, const QString &value) {\n";
tcpp << "\tif (key < lngkeys_cnt) {\n";
tcpp << "\t\t_found[key] = 1;\n";
tcpp << "\t\tif (_langValuesOriginal[key].isEmpty()) {\n";
tcpp << "\t\t\t_langValuesOriginal[key] = _langValues[key].isEmpty() ? qsl(\"{}\") : _langValues[key];\n";
tcpp << "\t\t}\n";
tcpp << "\t\t_langValues[key] = value;\n";
tcpp << "\t\treturn true;\n";
tcpp << "\t}\n";
tcpp << "\treturn false;\n";
tcpp << "}\n\n";
}
QFile cpp(lang_cpp), h(lang_h);
bool write_cpp = true, write_h = true;
if (cpp.open(QIODevice::ReadOnly)) {
QByteArray wasCpp = cpp.readAll();
if (wasCpp.size() == cppText.size()) {
if (!memcmp(wasCpp.constData(), cppText.constData(), cppText.size())) {
write_cpp = false;
}
}
cpp.close();
}
if (write_cpp) {
if (!cpp.open(QIODevice::WriteOnly)) throw Exception("Could not open lang.cpp for writing!");
if (cpp.write(cppText) != cppText.size()) throw Exception("Could not open lang.cpp for writing!");
}
if (h.open(QIODevice::ReadOnly)) {
QByteArray wasH = h.readAll();
if (wasH.size() == hText.size()) {
if (!memcmp(wasH.constData(), hText.constData(), hText.size())) {
write_h = false;
}
}
h.close();
}
if (write_h) {
if (!h.open(QIODevice::WriteOnly)) throw Exception("Could not open lang.h for writing!");
if (h.write(hText) != hText.size()) throw Exception("Could not open lang.h for writing!");
}
} catch (exception &e) {
cout << e.what() << "\n";
QCoreApplication::exit(1);
return false;
}
return true;
}

View File

@@ -1,78 +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-2017 John Preston, https://desktop.telegram.org
*/
#include <QtCore/QMap>
#include <QtCore/QVector>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QRegularExpression>
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <iostream>
#include <exception>
#include <QtCore/QTextStream>
#include <QtCore/QString>
#include <QtCore/QCoreApplication>
using std::string;
using std::cout;
using std::cerr;
using std::exception;
class Exception : public exception {
public:
Exception(const QString &msg) : _msg(msg.toUtf8()) {
}
virtual const char *what() const throw() {
return _msg.constData();
}
virtual ~Exception() throw() {
}
private:
QByteArray _msg;
};
bool genLang(const QString &lang_in, const QString &lang_out);
class GenLang : public QObject {
Q_OBJECT
public:
GenLang(const QString &lang_in, const QString &lang_out) : QObject(0),
_lang_in(lang_in), _lang_out(lang_out) {
}
public slots :
void run() {
if (genLang(_lang_in, _lang_out)) {
emit finished();
}
}
signals:
void finished();
private:
QString _lang_in, _lang_out;
};

View File

@@ -1,54 +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-2017 John Preston, https://desktop.telegram.org
*/
#include "mlmain.h"
int main(int argc, char *argv[]) {
QString lang_in("lang.strings"), lang_out("lang");
for (int i = 0; i < argc; ++i) {
if (string("-lang_in") == argv[i]) {
if (++i < argc) lang_in = argv[i];
} else if (string("-lang_out") == argv[i]) {
if (++i < argc) lang_out = argv[i];
}
}
#ifdef Q_OS_MAC
if (QDir(QString()).absolutePath() == "/") {
QString first = argc ? QString::fromLocal8Bit(argv[0]) : QString();
if (!first.isEmpty()) {
QFileInfo info(first);
if (info.exists()) {
QDir result(info.absolutePath() + "/../../..");
QString basePath = result.absolutePath() + '/';
lang_in = basePath + lang_in;
lang_out = basePath + lang_out;
}
}
}
#endif
QObject *taskImpl = new GenLang(lang_in, lang_out);
QCoreApplication a(argc, argv);
QObject::connect(taskImpl, SIGNAL(finished()), &a, SLOT(quit()));
QTimer::singleShot(0, taskImpl, SLOT(run()));
return a.exec();
}

View File

@@ -38,6 +38,7 @@ namespace {
constexpr auto kReloadChannelMembersTimeout = 1000; // 1 second wait before reload members in channel after adding
constexpr auto kSaveCloudDraftTimeout = 1000; // save draft to the cloud with 1 sec extra delay
constexpr auto kSaveDraftBeforeQuitTimeout = 1500; // give the app 1.5 secs to save drafts to cloud when quitting
constexpr auto kSmallDelayMs = 5;
} // namespace
@@ -45,7 +46,8 @@ constexpr auto kSmallDelayMs = 5;
ApiWrap::ApiWrap()
: _messageDataResolveDelayed([this] { resolveMessageDatas(); })
, _webPagesTimer([this] { resolveWebPages(); })
, _draftsSaveTimer([this] { saveDraftsToCloud(); }) {
, _draftsSaveTimer([this] { saveDraftsToCloud(); })
, _quitSavingDraftsTimer([] { App::allDraftsSaved(); }) {
Window::Theme::Background()->start();
}
@@ -1164,6 +1166,8 @@ void ApiWrap::saveDraftsToCloud() {
}
if (_draftsSaveRequestIds.isEmpty()) {
App::allDraftsSaved(); // can quit the application
} else if (App::quitting() && !_quitSavingDraftsTimer.isActive()) {
_quitSavingDraftsTimer.callOnce(kSaveDraftBeforeQuitTimeout);
}
}

View File

@@ -147,6 +147,7 @@ private:
QMap<History*, mtpRequestId> _draftsSaveRequestIds;
base::Timer _draftsSaveTimer;
base::Timer _quitSavingDraftsTimer;
OrderedSet<mtpRequestId> _stickerSetDisenableRequests;
Stickers::Order _stickersOrder;

View File

@@ -2466,9 +2466,6 @@ namespace {
if (auto apiwrap = api()) {
if (apiwrap->hasUnsavedDrafts()) {
apiwrap->saveDraftsToCloud();
QTimer::singleShot(SaveDraftBeforeQuitTimeout, [] {
QCoreApplication::quit();
});
return;
}
}
@@ -2764,12 +2761,19 @@ namespace {
}
void complexOverlayRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) {
auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
auto overlayParts = RectPart::Full | RectPart::None;
if (radius == ImageRoundRadius::Large) {
complexAdjustRect(corners, rect, overlayParts);
if (radius == ImageRoundRadius::Ellipse) {
PainterHighQualityEnabler hq(p);
p.setPen(Qt::NoPen);
p.setBrush(p.textPalette().selectOverlay);
p.drawEllipse(rect);
} else {
auto overlayCorners = (radius == ImageRoundRadius::Small) ? SelectedOverlaySmallCorners : SelectedOverlayLargeCorners;
auto overlayParts = RectPart::Full | RectPart::None;
if (radius == ImageRoundRadius::Large) {
complexAdjustRect(corners, rect, overlayParts);
}
roundRect(p, rect, p.textPalette().selectOverlay, overlayCorners, nullptr, overlayParts);
}
roundRect(p, rect, p.textPalette().selectOverlay, overlayCorners, nullptr, overlayParts);
}
void complexLocationRect(Painter &p, QRect rect, ImageRoundRadius radius, ImageRoundCorners corners) {

View File

@@ -25,6 +25,13 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "storage/file_download.h"
#include "storage/localstorage.h"
#include "window/notifications_manager.h"
#include "platform/platform_specific.h"
namespace {
constexpr auto kAutoLockTimeoutLateMs = TimeMs(3000);
} // namespace
QByteArray AuthSessionData::serialize() const {
auto size = sizeof(qint32) * 2;
@@ -83,6 +90,7 @@ void AuthSessionData::constructFromSerialized(const QByteArray &serialized) {
AuthSession::AuthSession(UserId userId)
: _userId(userId)
, _autoLockTimer([this] { checkAutoLock(); })
, _api(std::make_unique<ApiWrap>())
, _downloader(std::make_unique<Storage::Downloader>())
, _notifications(std::make_unique<Window::Notifications::System>(this)) {
@@ -90,10 +98,17 @@ AuthSession::AuthSession(UserId userId)
_saveDataTimer.setCallback([this] {
Local::writeUserSettings();
});
subscribe(Messenger::Instance().passcodedChanged(), [this] {
_shouldLockAt = 0;
notifications().updateAll();
});
}
bool AuthSession::Exists() {
return (Messenger::Instance().authSession() != nullptr);
if (auto messenger = Messenger::InstancePointer()) {
return (messenger->authSession() != nullptr);
}
return false;
}
AuthSession &AuthSession::Current() {
@@ -124,4 +139,29 @@ void AuthSession::saveDataDelayed(TimeMs delay) {
_saveDataTimer.callOnce(delay);
}
void AuthSession::checkAutoLock() {
if (!Global::LocalPasscode() || App::passcoded()) return;
Messenger::Instance().checkLocalTime();
auto now = getms(true);
auto shouldLockInMs = Global::AutoLock() * 1000LL;
auto idleForMs = psIdleTime();
auto notPlayingVideoForMs = now - data().lastTimeVideoPlayedAt();
auto checkTimeMs = qMin(idleForMs, notPlayingVideoForMs);
if (checkTimeMs >= shouldLockInMs || (_shouldLockAt > 0 && now > _shouldLockAt + kAutoLockTimeoutLateMs)) {
Messenger::Instance().setupPasscode();
} else {
_shouldLockAt = now + (shouldLockInMs - checkTimeMs);
_autoLockTimer.callOnce(shouldLockInMs - checkTimeMs);
}
}
void AuthSession::checkAutoLockIn(TimeMs time) {
if (_autoLockTimer.isActive()) {
auto remain = _autoLockTimer.remainingTime();
if (remain > 0 && remain <= time) return;
}
_autoLockTimer.callOnce(time);
}
AuthSession::~AuthSession() = default;

View File

@@ -79,6 +79,12 @@ public:
void setTabbedSelectorSectionEnabled(bool enabled) {
_variables.tabbedSelectorSectionEnabled = enabled;
}
void setLastTimeVideoPlayedAt(TimeMs time) {
_lastTimeVideoPlayedAt = time;
}
TimeMs lastTimeVideoPlayedAt() const {
return _lastTimeVideoPlayedAt;
}
private:
struct Variables {
@@ -92,10 +98,11 @@ private:
base::Observable<void> _moreChatsLoaded;
base::Observable<void> _savedGifsUpdated;
Variables _variables;
TimeMs _lastTimeVideoPlayedAt = 0;
};
class AuthSession final {
class AuthSession final : private base::Subscriber {
public:
AuthSession(UserId userId);
@@ -137,6 +144,9 @@ public:
return *_api;
}
void checkAutoLock();
void checkAutoLockIn(TimeMs time);
~AuthSession();
private:
@@ -144,6 +154,9 @@ private:
AuthSessionData _data;
base::Timer _saveDataTimer;
TimeMs _shouldLockAt = 0;
base::Timer _autoLockTimer;
const std::unique_ptr<ApiWrap> _api;
const std::unique_ptr<Storage::Downloader> _downloader;
const std::unique_ptr<Window::Notifications::System> _notifications;

View File

@@ -52,6 +52,6 @@ void AutoLockBox::durationChanged(int seconds) {
Local::writeUserSettings();
Global::RefLocalPasscodeChanged().notify();
App::wnd()->checkAutoLock();
AuthSession::Current().checkAutoLock();
closeBox();
}

View File

@@ -484,11 +484,6 @@ autoDownloadTopDelta: 10px;
autoDownloadTitlePosition: point(23px, 18px);
autoDownloadTitleFont: font(15px semibold);
editTextArea: InputField(defaultInputField) {
textMargins: margins(1px, 26px, 1px, 4px);
heightMax: 276px;
}
confirmCaptionArea: InputField(defaultInputField) {
textMargins: margins(1px, 26px, 1px, 4px);
heightMax: 78px;

View File

@@ -269,10 +269,9 @@ void AutoDownloadBox::onSave() {
bool enabledGroups = ((cAutoDownloadAudio() & dbiadNoGroups) && !(autoDownloadAudio & dbiadNoGroups));
cSetAutoDownloadAudio(autoDownloadAudio);
if (enabledPrivate || enabledGroups) {
const DocumentsData &data(App::documentsData());
for (DocumentsData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) {
if (i.value()->voice()) {
i.value()->automaticLoadSettingsChanged();
for (auto document : App::documentsData()) {
if (document->voice()) {
document->automaticLoadSettingsChanged();
}
}
}
@@ -284,10 +283,9 @@ void AutoDownloadBox::onSave() {
bool enabledGroups = ((cAutoDownloadGif() & dbiadNoGroups) && !(autoDownloadGif & dbiadNoGroups));
cSetAutoDownloadGif(autoDownloadGif);
if (enabledPrivate || enabledGroups) {
const DocumentsData &data(App::documentsData());
for (DocumentsData::const_iterator i = data.cbegin(), e = data.cend(); i != e; ++i) {
if (i.value()->isAnimation()) {
i.value()->automaticLoadSettingsChanged();
for (auto document : App::documentsData()) {
if (document->isAnimation()) {
document->automaticLoadSettingsChanged();
}
}
}

View File

@@ -345,7 +345,7 @@ void PasscodeBox::onSave(bool force) {
} else {
cSetPasscodeBadTries(0);
Local::setPasscode(pwd.toUtf8());
App::wnd()->checkAutoLock();
AuthSession::Current().checkAutoLock();
closeBox();
}
}

View File

@@ -450,46 +450,46 @@ void SendFilesBox::closeHook() {
}
}
EditCaptionBox::EditCaptionBox(QWidget*, HistoryItem *msg)
: _msgId(msg->fullId()) {
EditCaptionBox::EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId) : _msgId(msgId) {
Expects(media->canEditCaption());
QSize dimensions;
ImagePtr image;
QString caption;
DocumentData *doc = nullptr;
if (auto media = msg->getMedia()) {
auto t = media->type();
switch (t) {
case MediaTypeGif: {
_animated = true;
doc = static_cast<HistoryGif*>(media)->getDocument();
dimensions = doc->dimensions;
image = doc->thumb;
} break;
case MediaTypePhoto: {
_photo = true;
PhotoData *photo = static_cast<HistoryPhoto*>(media)->photo();
dimensions = QSize(photo->full->width(), photo->full->height());
image = photo->full;
} break;
switch (media->type()) {
case MediaTypeGif: {
_animated = true;
doc = static_cast<HistoryGif*>(media)->getDocument();
dimensions = doc->dimensions;
image = doc->thumb;
} break;
case MediaTypeVideo: {
_animated = true;
doc = static_cast<HistoryVideo*>(media)->getDocument();
dimensions = doc->dimensions;
image = doc->thumb;
} break;
case MediaTypePhoto: {
_photo = true;
auto photo = static_cast<HistoryPhoto*>(media)->photo();
dimensions = QSize(photo->full->width(), photo->full->height());
image = photo->full;
} break;
case MediaTypeFile:
case MediaTypeMusicFile:
case MediaTypeVoiceFile: {
_doc = true;
doc = static_cast<HistoryDocument*>(media)->getDocument();
image = doc->thumb;
} break;
}
caption = media->getCaption().text;
case MediaTypeVideo: {
_animated = true;
doc = static_cast<HistoryVideo*>(media)->getDocument();
dimensions = doc->dimensions;
image = doc->thumb;
} break;
case MediaTypeFile:
case MediaTypeMusicFile:
case MediaTypeVoiceFile: {
_doc = true;
doc = static_cast<HistoryDocument*>(media)->getDocument();
image = doc->thumb;
} break;
}
caption = media->getCaption().text;
if ((!_animated && (dimensions.isEmpty() || doc)) || image->isNull()) {
_animated = false;
if (image->isNull()) {
@@ -563,17 +563,11 @@ EditCaptionBox::EditCaptionBox(QWidget*, HistoryItem *msg)
_thumb = App::pixmapFromImageInPlace(_thumb.toImage().scaled(_thumbw * cIntRetinaFactor(), _thumbh * cIntRetinaFactor(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
_thumb.setDevicePixelRatio(cRetinaFactor());
}
if (_animated || _photo || _doc) {
_field.create(this, st::confirmCaptionArea, lang(lng_photo_caption), caption);
_field->setMaxLength(MaxPhotoCaption);
_field->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
} else {
auto original = msg->originalText();
auto text = textApplyEntities(original.text, original.entities);
_field.create(this, st::editTextArea, lang(lng_photo_caption), text);
// _field->setMaxLength(MaxMessageSize); // entities can make text in input field larger but still valid
_field->setCtrlEnterSubmit(cCtrlEnter() ? Ui::CtrlEnterSubmit::CtrlEnter : Ui::CtrlEnterSubmit::Enter);
}
t_assert(_animated || _photo || _doc);
_field.create(this, st::confirmCaptionArea, lang(lng_photo_caption), caption);
_field->setMaxLength(MaxPhotoCaption);
_field->setCtrlEnterSubmit(Ui::CtrlEnterSubmit::Both);
}
void EditCaptionBox::prepareGifPreview(DocumentData *document) {
@@ -613,20 +607,6 @@ void EditCaptionBox::clipCallback(Media::Clip::Notification notification) {
}
}
bool EditCaptionBox::canEdit(HistoryItem *message) {
if (auto media = message->getMedia()) {
switch (media->type()) {
case MediaTypeGif:
case MediaTypePhoto:
case MediaTypeVideo:
case MediaTypeFile:
case MediaTypeMusicFile:
case MediaTypeVoiceFile: return true;
}
}
return false;
}
void EditCaptionBox::prepare() {
addButton(lang(lng_settings_save), [this] { onSave(); });
addButton(lang(lng_cancel), [this] { closeBox(); });

View File

@@ -115,8 +115,7 @@ class EditCaptionBox : public BoxContent, public RPCSender {
Q_OBJECT
public:
EditCaptionBox(QWidget*, HistoryItem *msg);
static bool canEdit(HistoryItem *message);
EditCaptionBox(QWidget*, HistoryMedia *media, FullMsgId msgId);
public slots:
void onCaptionResized();

View File

@@ -98,8 +98,10 @@ void TabbedPanel::updateContentHeight() {
}
auto addedHeight = innerPadding().top() + innerPadding().bottom();
auto wantedContentHeight = qRound(st::emojiPanHeightRatio * _bottom) - addedHeight;
auto contentHeight = snap(wantedContentHeight, st::emojiPanMinHeight, st::emojiPanMaxHeight);
auto marginsHeight = _selector->marginTop() + _selector->marginBottom();
auto availableHeight = _bottom - marginsHeight;
auto wantedContentHeight = qRound(st::emojiPanHeightRatio * availableHeight) - addedHeight;
auto contentHeight = marginsHeight + snap(wantedContentHeight, st::emojiPanMinHeight, st::emojiPanMaxHeight);
auto resultTop = _bottom - addedHeight - contentHeight;
if (contentHeight == _contentHeight) {
move(x(), resultTop);

View File

@@ -333,10 +333,6 @@ TabbedSelector::TabbedSelector(QWidget *parent, gsl::not_null<Window::Controller
connect(gifs(), SIGNAL(selected(InlineBots::Result*, UserData*)), this, SIGNAL(inlineResultSelected(InlineBots::Result*, UserData*)));
connect(gifs(), SIGNAL(cancelled()), this, SIGNAL(cancelled()));
if (cPlatform() == dbipMac || cPlatform() == dbipMacOld) {
connect(App::wnd()->windowHandle(), SIGNAL(activeChanged()), this, SLOT(onWndActiveChanged()));
}
_topShadow->raise();
_bottomShadow->raise();
_tabsSlider->raise();

View File

@@ -61,6 +61,9 @@ public:
void beforeHiding();
void afterShown();
int marginTop() const;
int marginBottom() const;
bool preventAutoHide() const;
bool isSliding() const {
return _a_slide.animating();
@@ -135,8 +138,6 @@ private:
};
int marginTop() const;
int marginBottom() const;
void paintSlideFrame(Painter &p, TimeMs ms);
void paintContent(Painter &p);

View File

@@ -192,6 +192,9 @@ Type BasicTokenizedFile::readString() {
while (!reader_.atEnd()) {
auto ch = reader_.currentChar();
if (ch == '"') {
if (reader_.currentPtr() > offset) {
value.append(offset, reader_.currentPtr() - offset);
}
break;
}
if (ch == '\n') {
@@ -200,6 +203,9 @@ Type BasicTokenizedFile::readString() {
return Type::Invalid;
}
if (ch == '\\') {
if (reader_.currentPtr() > offset) {
value.append(offset, reader_.currentPtr() - offset);
}
reader_.skipChar();
ch = reader_.currentChar();
if (reader_.atEnd() || ch == '\n') {
@@ -207,9 +213,6 @@ Type BasicTokenizedFile::readString() {
failed_ = true;
return Type::Invalid;
}
if (reader_.currentPtr() > offset + 1) {
value.append(offset, reader_.currentPtr() - offset - 1);
}
offset = reader_.currentPtr() + 1;
if (ch == 'n') {
value.append('\n');
@@ -220,8 +223,6 @@ Type BasicTokenizedFile::readString() {
} else if (ch == '\\') {
value.append('\\');
}
} else {
value.append(ch);
}
reader_.skipChar();
}

View File

@@ -0,0 +1,495 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include "codegen/lang/generator.h"
#include <memory>
#include <functional>
#include <QtCore/QDir>
#include <QtCore/QSet>
#include <QtCore/QBuffer>
#include <QtGui/QImage>
#include <QtGui/QPainter>
namespace codegen {
namespace lang {
namespace {
constexpr auto kMaxPluralVariants = 6;
char hexChar(uchar ch) {
if (ch < 10) {
return '0' + ch;
} else if (ch < 16) {
return 'a' + (ch - 10);
}
return '0';
}
char hexSecondChar(char ch) {
return hexChar((*reinterpret_cast<uchar*>(&ch)) & 0x0F);
}
char hexFirstChar(char ch) {
return hexChar((*reinterpret_cast<uchar*>(&ch)) >> 4);
}
QString stringToEncodedString(const QString &str) {
QString result, lineBreak = "\\\n";
result.reserve(str.size() * 8);
bool writingHexEscapedCharacters = false, startOnNewLine = false;
int lastCutSize = 0;
auto utf = str.toUtf8();
for (auto ch : utf) {
if (result.size() - lastCutSize > 80) {
startOnNewLine = true;
result.append(lineBreak);
lastCutSize = result.size();
}
if (ch == '\n') {
writingHexEscapedCharacters = false;
result.append("\\n");
} else if (ch == '\t') {
writingHexEscapedCharacters = false;
result.append("\\t");
} else if (ch == '"' || ch == '\\') {
writingHexEscapedCharacters = false;
result.append('\\').append(ch);
} else if (ch < 32 || static_cast<uchar>(ch) > 127) {
writingHexEscapedCharacters = true;
result.append("\\x").append(hexFirstChar(ch)).append(hexSecondChar(ch));
} else {
if (writingHexEscapedCharacters) {
writingHexEscapedCharacters = false;
result.append("\"\"");
}
result.append(ch);
}
}
return '"' + (startOnNewLine ? lineBreak : QString()) + result + '"';
}
QString stringToEncodedString(const std::string &str) {
return stringToEncodedString(QString::fromStdString(str));
}
QString stringToBinaryArray(const std::string &str) {
QStringList rows, chars;
chars.reserve(13);
rows.reserve(1 + (str.size() / 13));
for (uchar ch : str) {
if (chars.size() > 12) {
rows.push_back(chars.join(", "));
chars.clear();
}
chars.push_back(QString("0x") + hexFirstChar(ch) + hexSecondChar(ch));
}
if (!chars.isEmpty()) {
rows.push_back(chars.join(", "));
}
return QString("{") + ((rows.size() > 1) ? '\n' : ' ') + rows.join(",\n") + " }";
}
} // namespace
Generator::Generator(const Langpack &langpack, const QString &destBasePath, const common::ProjectInfo &project)
: langpack_(langpack)
, basePath_(destBasePath)
, baseName_(QFileInfo(basePath_).baseName())
, project_(project) {
}
bool Generator::writeHeader() {
header_ = std::make_unique<common::CppFile>(basePath_ + ".h", project_);
header_->stream() << "\
class LangString : public QString {\n\
public:\n\
LangString() = default;\n\
LangString(const QString &str) : QString(str) {\n\
}\n\
LangString &operator=(const QString &str) {\n\
QString::operator=(str);\n\
return *this;\n\
}\n\
\n\
LangString tag(ushort tag, const QString &replacement);\n\
\n\
};\n\
\n\
LangString langCounted(ushort key0, ushort tag, float64 value);\n\
\n";
auto index = 0;
for (auto &tag : langpack_.tags) {
header_->stream() << "enum lngtag_" << tag.tag << " { lt_" << tag.tag << " = " << index++ << " };\n";
}
header_->stream() << "\
\n\
constexpr auto lngtags_cnt = " << langpack_.tags.size() << ";\n\
constexpr auto lngtags_max_counted_values = " << kMaxPluralVariants << ";\n\
\n\
enum LangKey {\n";
for (auto &entry : langpack_.entries) {
header_->stream() << "\t" << getFullKey(entry) << ",\n";
}
header_->stream() << "\
\n\
lngkeys_cnt,\n\
};\n\
\n\
LangString lang(LangKey key);\n\
\n\
LangString langOriginal(LangKey key);\n\
\n";
for (auto &entry : langpack_.entries) {
if (!entry.tags.empty()) {
auto &key = entry.key;
auto params = QStringList();
auto invokations = QStringList();
for (auto &tagData : entry.tags) {
auto &tag = tagData.tag;
auto isPlural = isTagPlural(key, tag);
params.push_back("lngtag_" + tag + ", " + (isPlural ? "float64 " : "const QString &") + tag + "__val");
invokations.push_back("tag(lt_" + tag + ", " + (isPlural ? ("langCounted(" + key + "__" + tag + "0, lt_" + tag + ", " + tag + "__val)") : (tag + "__val")) + ")");
}
header_->stream() << "\
inline LangString " << entry.key << "(" << params.join(QString(", ")) << ") {\n\
return lang(" << entry.key << "__tagged)." << invokations.join('.') << ";\n\
}\n\
\n";
}
}
return header_->finalize();
}
bool Generator::writeSource() {
source_ = std::make_unique<common::CppFile>(basePath_ + ".cpp", project_);
source_->include("lang.h").pushNamespace().stream() << "\
const char *_langKeyNames[lngkeys_cnt] = {\n\
\n";
for (auto &entry : langpack_.entries) {
source_->stream() << "\"" << entry.key << "\",\n";
}
source_->stream() << "\
\n\
};\n\
\n\
LangString _langValues[lngkeys_cnt], _langValuesOriginal[lngkeys_cnt];\n\
\n\
void set(LangKey key, const QString &val) {\n\
_langValues[key] = val;\n\
}\n\
\n\
class LangInit {\n\
public:\n\
LangInit() {\n";
for (auto &entry : langpack_.entries) {
source_->stream() << "\t\tset(" << getFullKey(entry) << ", QString::fromUtf8(" << stringToEncodedString(entry.value) << "));\n";
}
source_->stream() << "\
}\n\
\n\
};\n\
\n\
LangInit _langInit;\n\
\n";
source_->popNamespace().stream() << "\
\n\
LangString lang(LangKey key) {\n\
return (key < 0 || key > lngkeys_cnt) ? QString() : _langValues[key];\n\
}\n\
\n\
LangString langOriginal(LangKey key) {\n\
return (key < 0 || key > lngkeys_cnt || _langValuesOriginal[key] == qsl(\"{}\")) ? QString() : (_langValuesOriginal[key].isEmpty() ? _langValues[key] : _langValuesOriginal[key]);\n\
}\n\
\n\
const char *langKeyName(LangKey key) {\n\
return (key < 0 || key > lngkeys_cnt) ? \"\" : _langKeyNames[key];\n\
}\n\
\n\
ushort LangLoader::tagIndex(QLatin1String tag) const {\n\
auto size = tag.size();\n\
auto data = tag.data();\n";
auto tagsSet = std::set<QString, std::greater<QString>>();
for (auto &tag : langpack_.tags) {
tagsSet.insert(tag.tag);
}
writeSetSearch(tagsSet, [](const QString &tag) {
return "lt_" + tag;
}, "lngtags_cnt");
source_->stream() << "\
}\n\
\n\
LangKey LangLoader::keyIndex(QLatin1String key) const {\n\
auto size = key.size();\n\
auto data = key.data();\n";
auto taggedKeys = std::map<QString, QString>();
auto keysSet = std::set<QString, std::greater<QString>>();
for (auto &entry : langpack_.entries) {
if (entry.key.mid(0, entry.key.size() - 1).endsWith("__count")) {
continue;
}
auto full = getFullKey(entry);
if (full != entry.key) {
taggedKeys.emplace(entry.key, full);
}
keysSet.insert(entry.key);
}
writeSetSearch(keysSet, [&taggedKeys](const QString &key) {
auto it = taggedKeys.find(key);
return (it != taggedKeys.end()) ? it->second : key;
}, "lngkeys_cnt");
source_->stream() << "\
}\n\
\n\
bool LangLoader::tagReplaced(LangKey key, ushort tag) const {\n\
switch (key) {\n";
for (auto &entry : langpack_.entries) {
if (entry.tags.empty()) {
continue;
}
source_->stream() << "\
case " << entry.key << "__tagged: {\n\
switch (tag) {\n";
for (auto &tag : entry.tags) {
source_->stream() << "\
case lt_" << tag.tag << ":\n";
}
source_->stream() << "\
return true;\n\
}\n\
} break;\n";
}
source_->stream() << "\
}\
\n\
return false;\n\
}\n\
\n\
LangKey LangLoader::subkeyIndex(LangKey key, ushort tag, ushort index) const {\n\
if (index >= lngtags_max_counted_values) return lngkeys_cnt;\n\
\n\
switch (key) {\n";
for (auto &entry : langpack_.entries) {
auto cases = QString();
for (auto &tag : entry.tags) {
if (isTagPlural(entry.key, tag.tag)) {
cases += "\t\t\tcase lt_" + tag.tag + ": return LangKey(" + entry.key + "__" + tag.tag + "0 + index);\n";
}
}
if (cases.isEmpty()) {
continue;
}
source_->stream() << "\
case " << entry.key << "__tagged: {\n\
switch (tag) {\n\
" << cases << "\
}\n\
} break;\n";
}
source_->stream() << "\
}\n\
\n\
return lngkeys_cnt;\n\
}\n\
\n\
bool LangLoader::feedKeyValue(LangKey key, const QString &value) {\n\
if (key < lngkeys_cnt) {\n\
_found[key] = 1;\n\
if (_langValuesOriginal[key].isEmpty()) {\n\
_langValuesOriginal[key] = _langValues[key].isEmpty() ? qsl(\"{}\") : _langValues[key];\n\
}\n\
_langValues[key] = value;\n\
return true;\n\
}\n\
return false;\n\
}\n";
return source_->finalize();
}
template <typename ComputeResult>
void Generator::writeSetSearch(const std::set<QString, std::greater<QString>> &set, ComputeResult computeResult, const QString &invalidResult) {
auto tabs = [](int size) {
return QString(size, '\t');
};
enum class UsedCheckType {
Switch,
If,
UpcomingIf,
};
auto checkTypes = QVector<UsedCheckType>();
auto checkLengthHistory = QVector<int>(1, 0);
auto chars = QString();
auto tabsUsed = 1;
// Returns true if at least one check was finished.
auto finishChecksTillKey = [this, &chars, &checkTypes, &checkLengthHistory, &tabsUsed, tabs](const QString &key) {
auto result = false;
while (!chars.isEmpty() && key.midRef(0, chars.size()) != chars) {
result = true;
auto wasType = checkTypes.back();
chars.resize(chars.size() - 1);
checkTypes.pop_back();
checkLengthHistory.pop_back();
if (wasType == UsedCheckType::Switch || wasType == UsedCheckType::If) {
--tabsUsed;
if (wasType == UsedCheckType::Switch) {
source_->stream() << tabs(tabsUsed) << "break;\n";
}
if ((!chars.isEmpty() && key.midRef(0, chars.size()) != chars) || key == chars) {
source_->stream() << tabs(tabsUsed) << "}\n";
}
}
}
return result;
};
// Check if we can use "if" for a check on "charIndex" in "it" (otherwise only "switch")
auto canUseIfForCheck = [](auto it, auto end, int charIndex) {
auto key = *it;
auto i = it;
auto keyStart = key.mid(0, charIndex);
for (++i; i != end; ++i) {
auto nextKey = *i;
if (nextKey.mid(0, charIndex) != keyStart) {
return true;
} else if (nextKey.size() > charIndex && nextKey[charIndex] != key[charIndex]) {
return false;
}
}
return true;
};
auto countMinimalLength = [](auto it, auto end, int charIndex) {
auto key = *it;
auto i = it;
auto keyStart = key.mid(0, charIndex);
auto result = key.size();
for (++i; i != end; ++i) {
auto nextKey = *i;
if (nextKey.mid(0, charIndex) != keyStart) {
break;
} else if (nextKey.size() > charIndex && result > nextKey.size()) {
result = nextKey.size();
}
}
return result;
};
for (auto i = set.begin(), e = set.end(); i != e; ++i) {
// If we use just "auto" here and "name" becomes mutable,
// the operator[] will return QCharRef instead of QChar,
// and "auto ch = name[index]" will behave like "auto &ch =",
// if you assign something to "ch" after that you'll change "name" (!)
const auto name = *i;
auto weContinueOldSwitch = finishChecksTillKey(name);
while (chars.size() != name.size()) {
auto checking = chars.size();
auto partialKey = name.mid(0, checking);
auto keyChar = name[checking];
auto usedIfForCheckCount = 0;
auto minimalLengthCheck = countMinimalLength(i, e, checking);
for (; checking + usedIfForCheckCount != name.size(); ++usedIfForCheckCount) {
if (!canUseIfForCheck(i, e, checking + usedIfForCheckCount)
|| countMinimalLength(i, e, checking + usedIfForCheckCount) != minimalLengthCheck) {
break;
}
}
auto usedIfForCheck = !weContinueOldSwitch && (usedIfForCheckCount > 0);
auto checkLengthCondition = QString();
if (weContinueOldSwitch) {
weContinueOldSwitch = false;
} else {
checkLengthCondition = (minimalLengthCheck > checkLengthHistory.back()) ? ("size >= " + QString::number(minimalLengthCheck)) : QString();
if (!usedIfForCheck) {
source_->stream() << tabs(tabsUsed) << (checkLengthCondition.isEmpty() ? QString() : ("if (" + checkLengthCondition + ") ")) << "switch (data[" << checking << "]) {\n";
}
}
if (usedIfForCheck) {
auto conditions = QStringList();
if (usedIfForCheckCount > 1) {
conditions.push_back("!memcmp(data + " + QString::number(checking) + ", \"" + name.mid(checking, usedIfForCheckCount) + "\", " + QString::number(usedIfForCheckCount) + ")");
} else {
conditions.push_back("data[" + QString::number(checking) + "] == '" + keyChar + "'");
}
if (!checkLengthCondition.isEmpty()) {
conditions.push_front(checkLengthCondition);
}
source_->stream() << tabs(tabsUsed) << "if (" << conditions.join(" && ") << ") {\n";
checkTypes.push_back(UsedCheckType::If);
for (auto i = 1; i != usedIfForCheckCount; ++i) {
checkTypes.push_back(UsedCheckType::UpcomingIf);
chars.push_back(keyChar);
checkLengthHistory.push_back(qMax(minimalLengthCheck, checkLengthHistory.back()));
keyChar = name[checking + i];
}
} else {
source_->stream() << tabs(tabsUsed) << "case '" << keyChar << "':\n";
checkTypes.push_back(UsedCheckType::Switch);
}
++tabsUsed;
chars.push_back(keyChar);
checkLengthHistory.push_back(qMax(minimalLengthCheck, checkLengthHistory.back()));
}
source_->stream() << tabs(tabsUsed) << "return (size == " << chars.size() << ") ? " << computeResult(name) << " : " << invalidResult << ";\n";
}
finishChecksTillKey(QString());
source_->stream() << "\
\n\
return " << invalidResult << ";\n";
}
QString Generator::getFullKey(const Langpack::Entry &entry) {
if (entry.tags.empty()) {
return entry.key;
}
return entry.key + "__tagged";
}
bool Generator::isTagPlural(const QString &key, const QString &tag) const {
auto searchForKey = key + "__" + tag + "0";
for (auto &entry : langpack_.entries) {
if (entry.key == searchForKey) {
return true;
}
}
return false;
}
} // namespace lang
} // namespace codegen

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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include <memory>
#include <map>
#include <set>
#include <functional>
#include <QtCore/QString>
#include <QtCore/QSet>
#include "codegen/common/cpp_file.h"
#include "codegen/lang/parsed_file.h"
namespace codegen {
namespace lang {
class Generator {
public:
Generator(const Langpack &langpack, const QString &destBasePath, const common::ProjectInfo &project);
Generator(const Generator &other) = delete;
Generator &operator=(const Generator &other) = delete;
bool writeHeader();
bool writeSource();
private:
QString getFullKey(const Langpack::Entry &entry);
bool isTagPlural(const QString &key, const QString &tag) const;
template <typename ComputeResult>
void writeSetSearch(const std::set<QString, std::greater<QString>> &set, ComputeResult computeResult, const QString &invalidResult);
const Langpack &langpack_;
QString basePath_, baseName_;
const common::ProjectInfo &project_;
std::unique_ptr<common::CppFile> source_, header_;
};
} // namespace lang
} // namespace codegen

View File

@@ -0,0 +1,36 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include <QtCore/QCoreApplication>
#include "codegen/lang/options.h"
#include "codegen/lang/processor.h"
int main(int argc, char *argv[]) {
QCoreApplication app(argc, argv);
auto options = codegen::lang::parseOptions();
if (options.inputPath.isEmpty()) {
return -1;
}
codegen::lang::Processor processor(options);
return processor.launch();
}

View File

@@ -0,0 +1,87 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include "codegen/lang/options.h"
#include <ostream>
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
#include "codegen/common/logging.h"
namespace codegen {
namespace lang {
namespace {
constexpr int kErrorOutputPathExpected = 902;
constexpr int kErrorInputPathExpected = 903;
constexpr int kErrorSingleInputPathExpected = 904;
constexpr int kErrorWorkingPathExpected = 905;
} // namespace
using common::logError;
Options parseOptions() {
Options result;
auto args = QCoreApplication::instance()->arguments();
for (int i = 1, count = args.size(); i < count; ++i) { // skip first
auto &arg = args.at(i);
// Output path
if (arg == "-o") {
if (++i == count) {
logError(kErrorOutputPathExpected, "Command Line") << "output path expected after -o";
return Options();
} else {
result.outputPath = args.at(i);
}
} else if (arg.startsWith("-o")) {
result.outputPath = arg.mid(2);
// Working path
} else if (arg == "-w") {
if (++i == count) {
logError(kErrorWorkingPathExpected, "Command Line") << "working path expected after -w";
return Options();
} else {
common::logSetWorkingPath(args.at(i));
}
} else if (arg.startsWith("-w")) {
common::logSetWorkingPath(arg.mid(2));
// Input path
} else {
if (result.inputPath.isEmpty()) {
result.inputPath = arg;
} else {
logError(kErrorSingleInputPathExpected, "Command Line") << "only one input path expected";
return Options();
}
}
}
if (result.inputPath.isEmpty()) {
logError(kErrorInputPathExpected, "Command Line") << "input path expected";
return Options();
}
return result;
}
} // namespace lang
} // namespace codegen

View File

@@ -18,6 +18,21 @@ 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-2017 John Preston, https://desktop.telegram.org
*/
#include <QtCore/QTimer>
#pragma once
#include "genlang.h"
#include <QtCore/QString>
#include <QtCore/QStringList>
namespace codegen {
namespace lang {
struct Options {
QString outputPath = ".";
QString inputPath;
};
// Parsing failed if inputPath is empty in the result.
Options parseOptions();
} // namespace lang
} // namespace codegen

View File

@@ -0,0 +1,229 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include "codegen/lang/parsed_file.h"
#include <iostream>
#include <QtCore/QMap>
#include <QtCore/QDir>
#include <QtCore/QRegularExpression>
#include "codegen/common/basic_tokenized_file.h"
#include "codegen/common/logging.h"
namespace codegen {
namespace lang {
namespace {
using BasicToken = codegen::common::BasicTokenizedFile::Token;
using BasicType = BasicToken::Type;
constexpr int kErrorBadString = 806;
bool ValidateAnsiString(const QString &value) {
for (auto ch : value) {
if (ch.unicode() > 127) {
return false;
}
}
return true;
}
bool ValidateKey(const QString &key) {
static const auto validator = QRegularExpression("^[a-z0-9_.-]+$", QRegularExpression::CaseInsensitiveOption);
if (!validator.match(key).hasMatch()) {
return false;
}
if (key.indexOf("__") >= 0) {
return false;
}
return true;
}
bool ValidateTag(const QString &tag) {
static const auto validator = QRegularExpression("^[a-z0-9_]+$", QRegularExpression::CaseInsensitiveOption);
if (!validator.match(tag).hasMatch()) {
return false;
}
if (tag.indexOf("__") >= 0) {
return false;
}
return true;
}
QString PrepareCommandString(int index) {
static const QChar TextCommand(0x0010);
static const QChar TextCommandLangTag(0x0020);
auto result = QString(4, TextCommand);
result[1] = TextCommandLangTag;
result[2] = QChar(0x0020 + ushort(index));
return result;
}
} // namespace
ParsedFile::ParsedFile(const Options &options)
: filePath_(options.inputPath)
, file_(filePath_)
, options_(options) {
}
bool ParsedFile::read() {
if (!file_.read()) {
return false;
}
do {
if (auto keyToken = file_.getToken(BasicType::String)) {
if (ValidateKey(keyToken.value)) {
if (auto equals = file_.getToken(BasicType::Equals)) {
if (auto valueToken = file_.getToken(BasicType::String)) {
assertNextToken(BasicType::Semicolon);
addEntity(keyToken.value, valueToken.value);
continue;
} else {
logErrorUnexpectedToken() << "string value for '" << keyToken.value.toStdString() << "' key";
}
} else {
logErrorUnexpectedToken() << "'=' for '" << keyToken.value.toStdString() << "' key";
}
} else {
logErrorUnexpectedToken() << "string key name (/^[a-z0-9_.-]+$/i)";
}
}
if (file_.atEnd()) {
break;
}
logErrorUnexpectedToken() << "ansi string key name";
} while (!failed());
return !failed();
}
BasicToken ParsedFile::assertNextToken(BasicToken::Type type) {
auto result = file_.getToken(type);
if (!result) {
logErrorUnexpectedToken() << type;
}
return result;
}
common::LogStream ParsedFile::logErrorBadString() {
return logError(kErrorBadString);
}
QString ParsedFile::extractTagsData(const QString &value, Langpack *to) {
auto tagStart = value.indexOf('{');
if (tagStart < 0) {
return value;
}
auto tagEnd = 0;
auto finalValue = QString();
finalValue.reserve(value.size() * 2);
while (tagStart >= 0) {
if (tagStart > tagEnd) {
finalValue.append(value.midRef(tagEnd, tagStart - tagEnd));
}
++tagStart;
tagEnd = value.indexOf('}', tagStart);
if (tagEnd < 0) {
logErrorBadString() << "unexpected end of value, end of tag expected.";
return value;
}
finalValue.append(extractTagData(value.mid(tagStart, tagEnd - tagStart), to));
++tagEnd;
tagStart = value.indexOf('{', tagEnd);
}
if (tagEnd < value.size()) {
finalValue.append(value.midRef(tagEnd));
}
return finalValue;
}
QString ParsedFile::extractTagData(const QString &tagText, Langpack *to) {
auto numericPart = tagText.indexOf(':');
auto tag = (numericPart > 0) ? tagText.mid(0, numericPart) : tagText;
if (!ValidateTag(tag)) {
logErrorBadString() << "bad tag characters: '" << tagText.toStdString() << "'";
return QString();
}
for (auto &previousTag : to->tags) {
if (previousTag.tag == tag) {
logErrorBadString() << "duplicate found for tag '" << tagText.toStdString() << "'";
return QString();
}
}
auto index = 0;
auto tagIndex = result_.tags.size();
for (auto &alreadyTag : result_.tags) {
if (alreadyTag.tag == tag) {
tagIndex = index;
break;
}
++index;
}
if (tagIndex == result_.tags.size()) {
result_.tags.push_back({ tag });
}
if (numericPart > 0) {
auto numericParts = tagText.mid(numericPart + 1).split('|');
if (numericParts.size() != 3) {
logErrorBadString() << "bad option count for plural key part in tag: '" << tagText.toStdString() << "'";
return QString();
}
auto index = 0;
for (auto &part : numericParts) {
auto numericPartEntry = Langpack::Entry();
numericPartEntry.key = tag + QString::number(index++);
if (part.indexOf('#') != part.lastIndexOf('#')) {
logErrorBadString() << "bad option for plural key part in tag: '" << tagText.toStdString() << "', too many '#'.";
return QString();
}
numericPartEntry.value = part.replace('#', PrepareCommandString(tagIndex));
to->entries.push_back(numericPartEntry);
}
}
to->tags.push_back({ tag });
return PrepareCommandString(tagIndex);
}
void ParsedFile::addEntity(const QString &key, const QString &value) {
for (auto &entry : result_.entries) {
if (entry.key == key) {
logError(kErrorBadString) << "duplicate found for key '" << key.toStdString() << "'";
return;
}
}
auto tagsData = Langpack();
auto entry = Langpack::Entry();
entry.key = key;
entry.value = extractTagsData(value, &tagsData);
entry.tags = tagsData.tags;
result_.entries.push_back(entry);
for (auto &pluralEntry : tagsData.entries) {
auto taggedEntry = Langpack::Entry();
taggedEntry.key = key + "__" + pluralEntry.key;
taggedEntry.value = pluralEntry.value;
result_.entries.push_back(taggedEntry);
}
}
} // namespace lang
} // namespace codegen

View File

@@ -0,0 +1,99 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include <memory>
#include <string>
#include <functional>
#include <QImage>
#include "codegen/common/basic_tokenized_file.h"
#include "codegen/lang/options.h"
namespace codegen {
namespace lang {
struct Langpack {
struct Tag {
QString tag;
};
struct Entry {
QString key;
QString value;
std::vector<Tag> tags;
};
std::vector<Entry> entries;
std::vector<Tag> tags;
};
// Parses an input file to the internal struct.
class ParsedFile {
public:
explicit ParsedFile(const Options &options);
ParsedFile(const ParsedFile &other) = delete;
ParsedFile &operator=(const ParsedFile &other) = delete;
bool read();
Langpack getResult() {
return result_;
}
private:
bool failed() const {
return failed_ || file_.failed();
}
// Log error to std::cerr with 'code' at the current position in file.
common::LogStream logError(int code) {
failed_ = true;
return file_.logError(code);
}
common::LogStream logErrorUnexpectedToken() {
failed_ = true;
return file_.logErrorUnexpectedToken();
}
common::LogStream logErrorBadString();
common::LogStream logAssert(bool assertion) {
if (!assertion) {
return logError(common::kErrorInternal) << "internal - ";
}
return common::LogStream(common::LogStream::Null);
}
// Read next token and fire unexpected token error if it is not of "type".
using BasicToken = common::BasicTokenizedFile::Token;
BasicToken assertNextToken(BasicToken::Type type);
void addEntity(const QString &key, const QString &value);
QString extractTagsData(const QString &value, Langpack *to);
QString extractTagData(const QString &tag, Langpack *to);
QString filePath_;
common::BasicTokenizedFile file_;
Options options_;
bool failed_ = false;
Langpack result_;
};
} // namespace lang
} // namespace codegen

View File

@@ -0,0 +1,84 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#include "codegen/lang/processor.h"
#include <QtCore/QDir>
#include <QtCore/QFileInfo>
#include "codegen/common/cpp_file.h"
#include "codegen/lang/parsed_file.h"
#include "codegen/lang/generator.h"
namespace codegen {
namespace lang {
namespace {
constexpr int kErrorCantWritePath = 821;
} // namespace
Processor::Processor(const Options &options)
: parser_(std::make_unique<ParsedFile>(options))
, options_(options) {
}
int Processor::launch() {
if (!parser_->read()) {
return -1;
}
if (!write(parser_->getResult())) {
return -1;
}
return 0;
}
bool Processor::write(const Langpack &langpack) const {
bool forceReGenerate = false;
QDir dir(options_.outputPath);
if (!dir.mkpath(".")) {
common::logError(kErrorCantWritePath, "Command Line") << "can not open path for writing: " << dir.absolutePath().toStdString();
return false;
}
QFileInfo srcFile(options_.inputPath);
QString dstFilePath = dir.absolutePath() + "/lang_auto";
common::ProjectInfo project = {
"codegen_style",
srcFile.fileName(),
forceReGenerate
};
Generator generator(langpack, dstFilePath, project);
if (!generator.writeHeader()) {
return false;
}
if (!generator.writeSource()) {
return false;
}
return true;
}
Processor::~Processor() = default;
} // namespace lang
} // namespace codegen

View File

@@ -0,0 +1,53 @@
/*
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-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include <memory>
#include <QtCore/QString>
#include "codegen/lang/options.h"
namespace codegen {
namespace lang {
class ParsedFile;
struct Langpack;
// Walks through a file, parses it and generates the output.
class Processor {
public:
explicit Processor(const Options &options);
Processor(const Processor &other) = delete;
Processor &operator=(const Processor &other) = delete;
// Returns 0 on success.
int launch();
~Processor();
private:
bool write(const Langpack &langpack) const;
std::unique_ptr<ParsedFile> parser_;
const Options &options_;
};
} // namespace lang
} // namespace codegen

View File

@@ -31,6 +31,7 @@ namespace {
constexpr int kErrorOutputPathExpected = 902;
constexpr int kErrorInputPathExpected = 903;
constexpr int kErrorSingleInputPathExpected = 904;
constexpr int kErrorWorkingPathExpected = 905;
} // namespace
@@ -53,6 +54,17 @@ Options parseOptions() {
} else if (arg.startsWith("-o")) {
result.outputPath = arg.mid(2);
// Working path
} else if (arg == "-w") {
if (++i == count) {
logError(kErrorWorkingPathExpected, "Command Line") << "working path expected after -w";
return Options();
} else {
common::logSetWorkingPath(args.at(i));
}
} else if (arg.startsWith("-w")) {
common::logSetWorkingPath(arg.mid(2));
// Input path
} else {
if (result.inputPath.isEmpty()) {

View File

@@ -1133,11 +1133,11 @@ void initPxValues() {\n\
if (cRetina()) return;\n\
\n\
switch (cScale()) {\n";
for (int i = 1, scalesCount = scales.size(); i < scalesCount; ++i) {
source_->stream() << "\tcase " << scaleNames.at(i) << ":\n";
for (int i = 1, scalesCount = _scales.size(); i < scalesCount; ++i) {
source_->stream() << "\tcase " << _scaleNames.at(i) << ":\n";
for (auto it = pxValues_.cbegin(), e = pxValues_.cend(); it != e; ++it) {
auto value = it.key();
int adjusted = structure::data::pxAdjust(value, scales.at(i));
int adjusted = structure::data::pxAdjust(value, _scales.at(i));
if (adjusted != value) {
source_->stream() << "\t\t" << pxValueName(value) << " = " << adjusted << ";\n";
}

View File

@@ -77,8 +77,8 @@ private:
QMap<QString, int> iconMasks_; // icon file -> index
std::map<QString, int, std::greater<QString>> paletteIndices_;
std::vector<int> scales = { 4, 5, 6, 8 }; // scale / 4 gives our 1.00, 1.25, 1.50, 2.00
std::vector<const char *>scaleNames = { "dbisOne", "dbisOneAndQuarter", "dbisOneAndHalf", "dbisTwo" };
std::vector<int> _scales = { 4, 5, 6, 8 }; // scale / 4 gives our 1.00, 1.25, 1.50, 2.00
std::vector<const char *> _scaleNames = { "dbisOne", "dbisOneAndQuarter", "dbisOneAndHalf", "dbisTwo" };
};

View File

@@ -50,10 +50,6 @@ private:
std::unique_ptr<ParsedFile> parser_;
const Options &options_;
// List of files we need to generate with other instance of Generator.
// It is not empty only if rebuild_ flag is true.
QStringList dependenciesToGenerate_;
};
} // namespace style

View File

@@ -46,8 +46,6 @@ enum {
MTPTcpConnectionWaitTimeout = 2000, // 2 seconds waiting for tcp, until we accept http
MTPIPv4ConnectionWaitTimeout = 1000, // 1 seconds waiting for ipv4, until we accept ipv6
MTPUploadSessionsCount = 2, // max 2 upload sessions is created
MTPDownloadSessionsCount = 2, // max 2 download sessions is created
MTPKillFileSessionTimeout = 5000, // how much time without upload / download causes additional session kill
MTPDebugBufferSize = 1024 * 1024, // 1 mb start size
@@ -124,7 +122,6 @@ enum {
SaveDraftTimeout = 1000, // save draft after 1 secs of not changing text
SaveDraftAnywayTimeout = 5000, // or save anyway each 5 secs
SaveCloudDraftIdleTimeout = 14000, // save draft to the cloud after 14 more seconds
SaveDraftBeforeQuitTimeout = 1500, // give the app 1.5 secs to save drafts to cloud when quitting
SetOnlineAfterActivity = 30, // user with hidden last seen stays online for such amount of seconds in the interface
@@ -321,8 +318,6 @@ enum {
FileLoaderQueueStopTimeout = 5000,
UseBigFilesFrom = 10 * 1024 * 1024, // mtp big files methods used for files greater than 10mb
MaxFileQueries = 16, // max 16 file parts downloaded at the same time
MaxWebFileQueries = 8, // max 8 http[s] files downloaded at the same time
UploadPartSize = 32 * 1024, // 32kb for photo
DocumentMaxPartsCount = 3000, // no more than 3000 parts
@@ -331,7 +326,6 @@ enum {
DocumentUploadPartSize2 = 128 * 1024, // 128kb for small document ( <= 375mb )
DocumentUploadPartSize3 = 256 * 1024, // 256kb for medium document ( <= 750mb )
DocumentUploadPartSize4 = 512 * 1024, // 512kb for large document ( <= 1500mb )
MaxUploadFileParallelSize = MTPUploadSessionsCount * 512 * 1024, // max 512kb uploaded at the same time in each session
UploadRequestInterval = 500, // one part each half second, if not uploaded faster
MaxPhotosInMemory = 50, // try to clear some memory after 50 photos are created

View File

@@ -219,7 +219,7 @@ inline void copy_bytes(byte_span destination, const_byte_span source) {
#define for_const(range_declaration, range_expression) for (range_declaration : std::as_const(range_expression))
template <typename Enum>
inline QFlags<Enum> qFlags(Enum v) {
inline constexpr QFlags<Enum> qFlags(Enum v) {
return QFlags<Enum>(v);
}

View File

@@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#define BETA_VERSION_MACRO (0ULL)
constexpr int AppVersion = 1000030;
constexpr str_const AppVersionStr = "1.0.30";
constexpr int AppVersion = 1000033;
constexpr str_const AppVersionStr = "1.0.33";
constexpr bool AppAlphaVersion = true;
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;

View File

@@ -45,6 +45,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "autoupdater.h"
#include "observer_peer.h"
#include "auth_session.h"
#include "messenger.h"
#include "window/notifications_manager.h"
#include "ui/effects/widget_fade_wrap.h"
#include "window/window_controller.h"
@@ -2305,7 +2306,7 @@ DialogsWidget::DialogsWidget(QWidget *parent, gsl::not_null<Window::Controller*>
subscribe(Global::RefLocalPasscodeChanged(), [this] { updateLockUnlockVisibility(); });
_lockUnlock->setClickedCallback([this] {
_lockUnlock->setIconOverride(&st::dialogsUnlockIcon, &st::dialogsUnlockIconOver);
App::wnd()->setupPasscode();
Messenger::Instance().setupPasscode();
_lockUnlock->setIconOverride(nullptr);
});
_mainMenuToggle->setClickedCallback([this] { showMainMenu(); });

View File

@@ -587,7 +587,6 @@ namespace Global {
namespace internal {
struct Data {
uint64 LaunchId = 0;
SingleQueuedInvokation HandleHistoryUpdate = { [] { App::app()->call_handleHistoryUpdate(); } };
SingleQueuedInvokation HandleUnreadCounterUpdate = { [] { App::app()->call_handleUnreadCounterUpdate(); } };
SingleQueuedInvokation HandleDelayedPeerUpdates = { [] { App::app()->call_handleDelayedPeerUpdates(); } };
@@ -697,8 +696,6 @@ bool started() {
void start() {
GlobalData = new internal::Data();
memset_rand(&GlobalData->LaunchId, sizeof(GlobalData->LaunchId));
}
void finish() {
@@ -706,7 +703,6 @@ void finish() {
GlobalData = nullptr;
}
DefineReadOnlyVar(Global, uint64, LaunchId);
DefineRefVar(Global, SingleQueuedInvokation, HandleHistoryUpdate);
DefineRefVar(Global, SingleQueuedInvokation, HandleUnreadCounterUpdate);
DefineRefVar(Global, SingleQueuedInvokation, HandleDelayedPeerUpdates);

View File

@@ -291,7 +291,6 @@ bool started();
void start();
void finish();
DeclareReadOnlyVar(uint64, LaunchId);
DeclareRefVar(SingleQueuedInvokation, HandleHistoryUpdate);
DeclareRefVar(SingleQueuedInvokation, HandleUnreadCounterUpdate);
DeclareRefVar(SingleQueuedInvokation, HandleDelayedPeerUpdates);

View File

@@ -41,6 +41,8 @@ constexpr int kStatusShowClientsideRecordVideo = 6000;
constexpr int kStatusShowClientsideUploadVideo = 6000;
constexpr int kStatusShowClientsideRecordVoice = 6000;
constexpr int kStatusShowClientsideUploadVoice = 6000;
constexpr int kStatusShowClientsideRecordRound = 6000;
constexpr int kStatusShowClientsideUploadRound = 6000;
constexpr int kStatusShowClientsideUploadPhoto = 6000;
constexpr int kStatusShowClientsideUploadFile = 6000;
constexpr int kStatusShowClientsideChooseLocation = 6000;
@@ -212,6 +214,8 @@ bool History::updateSendActionNeedsAnimating(UserData *user, const MTPSendMessag
case mtpc_sendMessageUploadVideoAction: _sendActions.insert(user, { Type::UploadVideo, ms + kStatusShowClientsideUploadVideo, action.c_sendMessageUploadVideoAction().vprogress.v }); break;
case mtpc_sendMessageRecordAudioAction: _sendActions.insert(user, { Type::RecordVoice, ms + kStatusShowClientsideRecordVoice }); break;
case mtpc_sendMessageUploadAudioAction: _sendActions.insert(user, { Type::UploadVoice, ms + kStatusShowClientsideUploadVoice, action.c_sendMessageUploadAudioAction().vprogress.v }); break;
case mtpc_sendMessageRecordRoundAction: _sendActions.insert(user, { Type::RecordRound, ms + kStatusShowClientsideRecordRound }); break;
case mtpc_sendMessageUploadRoundAction: _sendActions.insert(user, { Type::UploadRound, ms + kStatusShowClientsideUploadRound }); break;
case mtpc_sendMessageUploadPhotoAction: _sendActions.insert(user, { Type::UploadPhoto, ms + kStatusShowClientsideUploadPhoto, action.c_sendMessageUploadPhotoAction().vprogress.v }); break;
case mtpc_sendMessageUploadDocumentAction: _sendActions.insert(user, { Type::UploadFile, ms + kStatusShowClientsideUploadFile, action.c_sendMessageUploadDocumentAction().vprogress.v }); break;
case mtpc_sendMessageGeoLocationAction: _sendActions.insert(user, { Type::ChooseLocation, ms + kStatusShowClientsideChooseLocation }); break;
@@ -299,6 +303,8 @@ bool History::updateSendActionNeedsAnimating(TimeMs ms, bool force) {
case Type::UploadVideo: return name.isEmpty() ? lang(lng_send_action_upload_video) : lng_user_action_upload_video(lt_user, name);
case Type::RecordVoice: return name.isEmpty() ? lang(lng_send_action_record_audio) : lng_user_action_record_audio(lt_user, name);
case Type::UploadVoice: return name.isEmpty() ? lang(lng_send_action_upload_audio) : lng_user_action_upload_audio(lt_user, name);
case Type::RecordRound: return name.isEmpty() ? lang(lng_send_action_record_round) : lng_user_action_record_round(lt_user, name);
case Type::UploadRound: return name.isEmpty() ? lang(lng_send_action_upload_round) : lng_user_action_upload_round(lt_user, name);
case Type::UploadPhoto: return name.isEmpty() ? lang(lng_send_action_upload_photo) : lng_user_action_upload_photo(lt_user, name);
case Type::UploadFile: return name.isEmpty() ? lang(lng_send_action_upload_file) : lng_user_action_upload_file(lt_user, name);
case Type::ChooseLocation: return name.isEmpty() ? lang(lng_send_action_geo_location) : lng_user_action_geo_location(lt_user, name);

View File

@@ -418,3 +418,7 @@ msgWaveformBar: 2px;
msgWaveformSkip: 1px;
msgWaveformMin: 2px;
msgWaveformMax: 20px;
historyVideoMessageMute: icon {{ "volume_mute", historyFileThumbIconFg }};
historyVideoMessageMuteSelected: icon {{ "volume_mute", historyFileThumbIconFgSelected }};
historyVideoMessageMuteSize: 25px;

View File

@@ -1200,6 +1200,10 @@ void HistoryInner::mouseDoubleClickEvent(QMouseEvent *e) {
}
}
void HistoryInner::contextMenuEvent(QContextMenuEvent *e) {
showContextMenu(e);
}
void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
if (_menu) {
_menu->deleteLater();

View File

@@ -41,8 +41,6 @@ public:
void messagesReceived(PeerData *peer, const QVector<MTPMessage> &messages);
void messagesReceivedDown(PeerData *peer, const QVector<MTPMessage> &messages);
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
TextWithEntities getSelectedText() const;
void dragActionStart(const QPoint &screenPos, Qt::MouseButton button = Qt::LeftButton);
@@ -108,8 +106,9 @@ protected:
void leaveEventHook(QEvent *e) override;
void resizeEvent(QResizeEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
public slots:
public slots:
void onUpdateSelected();
void onParentGeometryChanged();
@@ -126,11 +125,13 @@ protected:
void onTouchScrollTimer();
void onDragExec();
private slots:
private slots:
void onScrollDateCheck();
void onScrollDateHideByTimer();
private:
void showContextMenu(QContextMenuEvent *e, bool showFromTouch = false);
void itemRemoved(HistoryItem *item);
void savePhotoToFile(PhotoData *photo);
void saveDocumentToFile(DocumentData *document);

View File

@@ -756,20 +756,19 @@ void HistoryItem::setId(MsgId newId) {
bool HistoryItem::canEdit(const QDateTime &cur) const {
auto messageToMyself = (_history->peer->id == AuthSession::CurrentUserPeerId());
auto messageTooOld = messageToMyself ? false : (date.secsTo(cur) >= Global::EditTimeLimit());
if (id < 0 || messageTooOld) return false;
if (id < 0 || messageTooOld) {
return false;
}
if (auto msg = toHistoryMessage()) {
if (msg->Has<HistoryMessageVia>() || msg->Has<HistoryMessageForwarded>()) return false;
if (msg->Has<HistoryMessageVia>() || msg->Has<HistoryMessageForwarded>()) {
return false;
}
if (auto media = msg->getMedia()) {
auto type = media->type();
if (type != MediaTypePhoto &&
type != MediaTypeVideo &&
type != MediaTypeFile &&
type != MediaTypeGif &&
type != MediaTypeMusicFile &&
type != MediaTypeVoiceFile &&
type != MediaTypeWebPage) {
if (media->canEditCaption()) {
return true;
} else if (media->type() != MediaTypeWebPage) {
return false;
}
}
@@ -785,8 +784,12 @@ bool HistoryItem::canEdit(const QDateTime &cur) const {
bool HistoryItem::canDeleteForEveryone(const QDateTime &cur) const {
auto messageToMyself = (_history->peer->id == AuthSession::CurrentUserPeerId());
auto messageTooOld = messageToMyself ? false : (date.secsTo(cur) >= Global::EditTimeLimit());
if (id < 0 || messageToMyself || messageTooOld) return false;
if (history()->peer->isChannel()) return false;
if (id < 0 || messageToMyself || messageTooOld) {
return false;
}
if (history()->peer->isChannel()) {
return false;
}
if (auto msg = toHistoryMessage()) {
return !isPost() && out();
@@ -803,21 +806,31 @@ bool HistoryItem::unread() const {
if (out()) {
// Outgoing messages in converted chats are always read.
if (history()->peer->migrateTo()) return false;
if (history()->peer->migrateTo()) {
return false;
}
if (id > 0) {
if (id < history()->outboxReadBefore) return false;
if (id < history()->outboxReadBefore) {
return false;
}
if (auto user = history()->peer->asUser()) {
if (user->botInfo) return false;
if (user->botInfo) {
return false;
}
} else if (auto channel = history()->peer->asChannel()) {
if (!channel->isMegagroup()) return false;
if (!channel->isMegagroup()) {
return false;
}
}
}
return true;
}
if (id > 0) {
if (id < history()->inboxReadBefore) return false;
if (id < history()->inboxReadBefore) {
return false;
}
return true;
}
return (_flags & MTPDmessage_ClientFlag::f_clientside_unread);
@@ -867,25 +880,32 @@ void HistoryItem::setUnreadBarFreezed() {
void HistoryItem::clipCallback(Media::Clip::Notification notification) {
using namespace Media::Clip;
HistoryMedia *media = getMedia();
if (!media) return;
auto media = getMedia();
if (!media) {
return;
}
Reader *reader = media ? media->getClipReader() : 0;
if (!reader) return;
auto reader = media ? media->getClipReader() : nullptr;
if (!reader) {
return;
}
switch (notification) {
case NotificationReinit: {
bool stopped = false;
auto stopped = false;
if (reader->autoPausedGif()) {
if (MainWidget *m = App::main()) {
if (auto m = App::main()) {
if (!m->isItemVisible(this)) { // stop animation if it is not visible
media->stopInline();
if (DocumentData *document = media->getDocument()) { // forget data from memory
if (auto document = media->getDocument()) { // forget data from memory
document->forget();
}
stopped = true;
}
}
} else if (reader->mode() == Media::Clip::Reader::Mode::Video && reader->state() == Media::Clip::State::Finished) {
// Stop finished video message.
media->stopInline();
}
if (!stopped) {
setPendingInitDimensions();
@@ -903,7 +923,9 @@ void HistoryItem::clipCallback(Media::Clip::Notification notification) {
void HistoryItem::recountDisplayDate() {
bool displayingDate = ([this]() {
if (isEmpty()) return false;
if (isEmpty()) {
return false;
}
if (auto previous = previousItem()) {
return previous->isEmpty() || (previous->date.date() != date.date());
@@ -930,7 +952,9 @@ QString HistoryItem::notificationText() const {
};
auto result = getText();
if (result.size() > 0xFF) result = result.mid(0, 0xFF) + qsl("...");
if (result.size() > 0xFF) {
result = result.mid(0, 0xFF) + qsl("...");
}
return result;
}

View File

@@ -171,6 +171,10 @@ public:
return false;
}
virtual bool canEditCaption() const {
return false;
}
// Sometimes click on media in message is overloaded by the messsage:
// (for example it can open a link or a game instead of opening media)
// But the overloading click handler should be used only when media

View File

@@ -1044,7 +1044,7 @@ void HistoryDocument::initDimensions() {
} else {
tleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
tright = st::msgFileThumbPadding.left();
int32 unread = _data->voice() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0;
auto unread = _data->voice() ? (st::mediaUnreadSkip + st::mediaUnreadSize) : 0;
_maxw = qMax(_maxw, tleft + documentMaxStatusWidth(_data) + unread + _parent->skipBlockWidth() + st::msgPadding.right());
}
@@ -1653,7 +1653,7 @@ HistoryGif::HistoryGif(HistoryItem *parent, DocumentData *document, const QStrin
setStatusSize(FileStatusSizeReady);
if (!caption.isEmpty()) {
if (!caption.isEmpty() && !_data->isRoundVideo()) {
_caption.setText(st::messageTextStyle, caption + _parent->skipBlock(), itemTextNoMonoOptions(_parent));
}
@@ -1681,8 +1681,7 @@ void HistoryGif::initDimensions() {
if (!_gif->autoplay()) {
Ui::show(Box<InformBox>(lang(lng_gif_error)));
}
App::unregGifItem(_gif.get());
_gif.setBad();
setClipReader(Media::Clip::ReaderPointer::Bad());
}
if (_gif && _gif->ready()) {
@@ -1768,11 +1767,15 @@ int HistoryGif::resizeGetHeight(int width) {
_width = qMax(_width, _parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x()));
if (_gif && _gif->ready()) {
if (!_gif->started()) {
auto isRound = _data->isRoundVideo();
auto inWebPage = (_parent->getMedia() != this);
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
auto roundCorners = inWebPage ? ImageRoundCorner::All : ((isBubbleTop() ? (ImageRoundCorner::TopLeft | ImageRoundCorner::TopRight) : ImageRoundCorner::None)
auto roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
auto roundCorners = (isRound || inWebPage) ? ImageRoundCorner::All : ((isBubbleTop() ? (ImageRoundCorner::TopLeft | ImageRoundCorner::TopRight) : ImageRoundCorner::None)
| ((isBubbleBottom() && _caption.isEmpty()) ? (ImageRoundCorner::BottomLeft | ImageRoundCorner::BottomRight) : ImageRoundCorner::None));
_gif->start(_thumbw, _thumbh, _width, _height, roundRadius, roundCorners);
if (isRound) {
Media::Player::mixer()->setVideoVolume(1.);
}
}
} else {
_width = qMax(_width, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x()));
@@ -1796,10 +1799,12 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
if (_width < st::msgPadding.left() + st::msgPadding.right() + 1) return;
_data->automaticLoad(_parent);
bool loaded = _data->loaded(), displayLoading = (_parent->id < 0) || _data->displayLoading();
bool selected = (selection == FullSelection);
auto loaded = _data->loaded();
auto displayLoading = (_parent->id < 0) || _data->displayLoading();
auto selected = (selection == FullSelection);
if (loaded && !_gif && !_gif.isBad() && cAutoPlayGif()) {
auto videoFinished = _gif && (_gif->mode() == Media::Clip::Reader::Mode::Video) && (_gif->state() == Media::Clip::State::Finished);
if (loaded && cAutoPlayGif() && ((!_gif && !_gif.isBad()) || videoFinished)) {
Ui::autoplayMediaInlineAsync(_parent->fullId());
}
@@ -1807,9 +1812,11 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
bool bubble = _parent->hasBubble();
bool out = _parent->out(), isPost = _parent->isPost(), outbg = out && !isPost;
int32 captionw = width - st::msgPadding.left() - st::msgPadding.right();
auto captionw = width - st::msgPadding.left() - st::msgPadding.right();
bool animating = (_gif && _gif->started());
auto isRound = _data->isRoundVideo();
auto displayMute = false;
auto animating = (_gif && _gif->started());
if (!animating || _parent->id < 0) {
if (displayLoading) {
@@ -1819,8 +1826,10 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
}
}
updateStatusText();
} else if (_gif && _gif->mode() == Media::Clip::Reader::Mode::Video) {
updateStatusText();
}
bool radial = isRadialAnimation(ms);
auto radial = isRadialAnimation(ms);
if (bubble) {
skipx = st::mediaPadding.left();
@@ -1834,18 +1843,25 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
height -= st::msgPadding.bottom();
}
}
} else {
} else if (!isRound) {
App::roundShadow(p, 0, 0, width, _height, selected ? st::msgInShadowSelected : st::msgInShadow, selected ? InSelectedShadowCorners : InShadowCorners);
}
QRect rthumb(rtlrect(skipx, skipy, width, height, _width));
auto inWebPage = (_parent->getMedia() != this);
auto roundRadius = inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
auto roundCorners = inWebPage ? ImageRoundCorner::All : ((isBubbleTop() ? (ImageRoundCorner::TopLeft | ImageRoundCorner::TopRight) : ImageRoundCorner::None)
auto roundRadius = isRound ? ImageRoundRadius::Ellipse : inWebPage ? ImageRoundRadius::Small : ImageRoundRadius::Large;
auto roundCorners = (isRound || inWebPage) ? ImageRoundCorner::All : ((isBubbleTop() ? (ImageRoundCorner::TopLeft | ImageRoundCorner::TopRight) : ImageRoundCorner::None)
| ((isBubbleBottom() && _caption.isEmpty()) ? (ImageRoundCorner::BottomLeft | ImageRoundCorner::BottomRight) : ImageRoundCorner::None));
if (animating) {
auto paused = App::wnd()->controller()->isGifPausedAtLeastFor(Window::GifPauseReason::Any);
if (isRound) {
if (_gif->mode() == Media::Clip::Reader::Mode::Video) {
paused = false;
} else {
displayMute = true;
}
}
p.drawPixmap(rthumb.topLeft(), _gif->current(_thumbw, _thumbh, width, height, roundRadius, roundCorners, paused ? 0 : ms));
} else {
p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, _thumbh, width, height, roundRadius, roundCorners));
@@ -1855,8 +1871,8 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
}
if (radial || _gif.isBad() || (!_gif && ((!loaded && !_data->loading()) || !cAutoPlayGif()))) {
float64 radialOpacity = (radial && loaded && _parent->id > 0) ? _animation->radial.opacity() : 1;
QRect inner(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
auto radialOpacity = (radial && loaded && _parent->id > 0) ? _animation->radial.opacity() : 1.;
auto inner = QRect(rthumb.x() + (rthumb.width() - st::msgFileSize) / 2, rthumb.y() + (rthumb.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen);
if (selected) {
p.setBrush(st::msgDateImgBgSelected);
@@ -1895,23 +1911,73 @@ void HistoryGif::draw(Painter &p, const QRect &r, TextSelection selection, TimeM
_animation->radial.draw(p, rinner, st::msgFileRadialLine, selected ? st::historyFileThumbRadialFgSelected : st::historyFileThumbRadialFg);
}
if (!animating || _parent->id < 0) {
int32 statusX = skipx + st::msgDateImgDelta + st::msgDateImgPadding.x(), statusY = skipy + st::msgDateImgDelta + st::msgDateImgPadding.y();
int32 statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
int32 statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
if (!isRound && (!animating || _parent->id < 0)) {
auto statusX = skipx + st::msgDateImgDelta + st::msgDateImgPadding.x();
auto statusY = skipy + st::msgDateImgDelta + st::msgDateImgPadding.y();
auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, _width), selected ? st::msgDateImgBgSelected : st::msgDateImgBg, selected ? DateSelectedCorners : DateCorners);
p.setFont(st::normalFont);
p.setPen(st::msgDateImgFg);
p.drawTextLeft(statusX, statusY, _width, _statusText, statusW - 2 * st::msgDateImgPadding.x());
}
}
if (displayMute) {
auto muteRect = rtlrect(rthumb.x() + (rthumb.width() - st::historyVideoMessageMuteSize) / 2, rthumb.y() + st::msgDateImgDelta, st::historyVideoMessageMuteSize, st::historyVideoMessageMuteSize, _width);
p.setPen(Qt::NoPen);
p.setBrush(selected ? st::msgDateImgBgSelected : st::msgDateImgBg);
PainterHighQualityEnabler hq(p);
p.drawEllipse(muteRect);
(selected ? st::historyVideoMessageMuteSelected : st::historyVideoMessageMute).paintInCenter(p, muteRect);
}
if (isRound) {
auto mediaUnread = _parent->isMediaUnread();
auto statusW = st::normalFont->width(_statusText) + 2 * st::msgDateImgPadding.x();
auto statusH = st::normalFont->height + 2 * st::msgDateImgPadding.y();
auto statusX = skipx + st::msgDateImgDelta + st::msgDateImgPadding.x();
auto statusY = skipy + height - st::msgDateImgDelta - statusH + st::msgDateImgPadding.y();
if (_parent->isMediaUnread()) {
statusW += st::mediaUnreadSkip + st::mediaUnreadSize;
}
App::roundRect(p, rtlrect(statusX - st::msgDateImgPadding.x(), statusY - st::msgDateImgPadding.y(), statusW, statusH, _width), selected ? st::msgServiceBgSelected : st::msgServiceBg, selected ? StickerSelectedCorners : StickerCorners);
p.setFont(st::normalFont);
p.setPen(st::msgServiceFg);
p.drawTextLeft(statusX, statusY, _width, _statusText, statusW - 2 * st::msgDateImgPadding.x());
if (mediaUnread) {
p.setPen(Qt::NoPen);
p.setBrush(st::msgServiceFg);
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(rtlrect(statusX - st::msgDateImgPadding.x() + statusW - st::msgDateImgPadding.x() - st::mediaUnreadSize, statusY + st::mediaUnreadTop, st::mediaUnreadSize, st::mediaUnreadSize, _width));
}
}
}
if (!_caption.isEmpty()) {
p.setPen(outbg ? (selected ? st::historyTextOutFgSelected : st::historyTextOutFg) : (selected ? st::historyTextInFgSelected : st::historyTextInFg));
_caption.draw(p, st::msgPadding.left(), skipy + height + st::mediaPadding.bottom() + st::mediaCaptionSkip, captionw, style::al_left, 0, -1, selection);
} else if (_parent->getMedia() == this && (_data->uploading() || App::hoveredItem() == _parent)) {
int32 fullRight = skipx + width, fullBottom = skipy + height;
_parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, InfoDisplayOverImage);
} else if (_parent->getMedia() == this && (isRound || _data->uploading() || App::hoveredItem() == _parent)) {
auto fullRight = skipx + width;
auto fullBottom = skipy + height;
if (isRound && !outbg) {
auto infoWidth = _parent->infoWidth();
// This is just some arbitrary point,
// the main idea is to make info left aligned here.
fullRight += infoWidth - st::normalFont->height;
auto maxRight = _parent->history()->width - st::msgMargin.left();
if (_parent->history()->canHaveFromPhotos()) {
maxRight -= st::msgMargin.right();
} else {
maxRight -= st::msgMargin.left();
}
if (fullRight > maxRight) {
fullRight = maxRight;
}
}
_parent->drawInfo(p, fullRight, fullBottom, 2 * skipx + width, selected, isRound ? InfoDisplayOverBackground : InfoDisplayOverImage);
}
}
@@ -1943,7 +2009,7 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) {
if (_data->uploading()) {
result.link = _cancell;
} else if (!_gif || !cAutoPlayGif()) {
} else if (!_gif || !cAutoPlayGif() || _data->isRoundVideo()) {
result.link = _data->loaded() ? _openl : (_data->loading() ? _cancell : _savel);
}
if (_parent->getMedia() == this) {
@@ -1959,19 +2025,31 @@ HistoryTextState HistoryGif::getState(int x, int y, HistoryStateRequest request)
}
QString HistoryGif::notificationText() const {
return captionedNotificationText(qsl("GIF"), _caption);
return captionedNotificationText(mediaTypeString(), _caption);
}
QString HistoryGif::inDialogsText() const {
return captionedInDialogsText(qsl("GIF"), _caption);
return captionedInDialogsText(mediaTypeString(), _caption);
}
TextWithEntities HistoryGif::selectedText(TextSelection selection) const {
return captionedSelectedText(qsl("GIF"), _caption, selection);
return captionedSelectedText(mediaTypeString(), _caption, selection);
}
QString HistoryGif::mediaTypeString() const {
return _data->isRoundVideo() ? lang(lng_in_dlg_video_message) : qsl("GIF");
}
void HistoryGif::setStatusSize(int32 newSize) const {
HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0);
if (_data->isRoundVideo()) {
if (newSize < 0) {
_statusText = formatDurationText(-newSize - 1);
} else {
_statusText = formatDurationText(_data->duration());
}
} else {
HistoryFileMedia::setStatusSize(newSize, _data->size, -2, 0);
}
}
void HistoryGif::updateStatusText() const {
@@ -1985,6 +2063,10 @@ void HistoryGif::updateStatusText() const {
statusSize = _data->loadOffset();
} else if (_data->loaded()) {
statusSize = FileStatusSizeLoaded;
if (_gif && _gif->mode() == Media::Clip::Reader::Mode::Video) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Video);
statusSize = -1 - (state.position / state.frequency);
}
} else {
statusSize = FileStatusSizeReady;
}
@@ -2016,35 +2098,60 @@ ImagePtr HistoryGif::replyPreview() {
}
bool HistoryGif::playInline(bool autoplay) {
using Mode = Media::Clip::Reader::Mode;
if (_data->isRoundVideo() && _gif) {
// Stop autoplayed silent video when we start playback by click.
// Stop finished video message when autoplay starts.
if ((!autoplay && _gif->mode() == Mode::Gif)
|| (autoplay && _gif->mode() == Mode::Video && _gif->state() == Media::Clip::State::Finished)) {
stopInline();
}
}
if (_gif) {
stopInline();
} else if (_data->loaded(DocumentData::FilePathResolveChecked)) {
if (!cAutoPlayGif()) {
App::stopGifItems();
}
_gif = Media::Clip::MakeReader(_data->location(), _data->data(), [this](Media::Clip::Notification notification) {
auto mode = (!autoplay && _data->isRoundVideo()) ? Mode::Video : Mode::Gif;
setClipReader(Media::Clip::MakeReader(_data->location(), _data->data(), [this](Media::Clip::Notification notification) {
_parent->clipCallback(notification);
});
App::regGifItem(_gif.get(), _parent);
if (_gif) _gif->setAutoplay();
}, mode));
if (mode == Mode::Video) {
if (App::main()) {
App::main()->mediaMarkRead(_data);
}
App::wnd()->controller()->enableGifPauseReason(Window::GifPauseReason::RoundPlaying);
}
if (_gif && autoplay) {
_gif->setAutoplay();
}
}
return true;
}
void HistoryGif::stopInline() {
if (_gif) {
App::unregGifItem(_gif.get());
if (_gif && _gif->mode() == Media::Clip::Reader::Mode::Video) {
App::wnd()->controller()->disableGifPauseReason(Window::GifPauseReason::RoundPlaying);
}
_gif.reset();
clearClipReader();
_parent->setPendingInitDimensions();
Notify::historyItemLayoutChanged(_parent);
}
HistoryGif::~HistoryGif() {
void HistoryGif::setClipReader(Media::Clip::ReaderPointer gif) {
if (_gif) {
App::unregGifItem(_gif.get());
}
_gif = std::move(gif);
if (_gif) {
App::regGifItem(_gif.get(), _parent);
}
}
HistoryGif::~HistoryGif() {
clearClipReader();
}
float64 HistoryGif::dataProgress() const {
@@ -2068,10 +2175,6 @@ HistorySticker::HistorySticker(HistoryItem *parent, DocumentData *document) : Hi
}
}
class TestClickHandler : public ClickHandler {
};
void HistorySticker::initDimensions() {
auto sticker = _data->sticker();

View File

@@ -175,6 +175,9 @@ public:
bool skipBubbleTail() const override {
return isBubbleBottom() && _caption.isEmpty();
}
bool canEditCaption() const override {
return true;
}
bool isReadyForOpen() const override {
return _data->loaded();
}
@@ -191,7 +194,7 @@ protected:
}
private:
PhotoData *_data;
gsl::not_null<PhotoData*> _data;
int16 _pixw = 1;
int16 _pixh = 1;
Text _caption;
@@ -266,6 +269,9 @@ public:
bool skipBubbleTail() const override {
return isBubbleBottom() && _caption.isEmpty();
}
bool canEditCaption() const override {
return true;
}
protected:
float64 dataProgress() const override {
@@ -279,7 +285,7 @@ protected:
}
private:
DocumentData *_data;
gsl::not_null<DocumentData*> _data;
int32 _thumbw;
Text _caption;
@@ -417,6 +423,9 @@ public:
bool hideForwardedFrom() const override {
return _data->song();
}
bool canEditCaption() const override {
return true;
}
void step_voiceProgress(float64 ms, bool timer);
@@ -445,7 +454,7 @@ private:
template <typename Callback>
void buildStringRepresentation(Callback callback) const;
DocumentData *_data;
gsl::not_null<DocumentData*> _data;
};
@@ -506,6 +515,9 @@ public:
return _caption.originalTextWithEntities();
}
bool needsBubble() const override {
if (_data->isRoundVideo()) {
return false;
}
if (!_caption.isEmpty()) {
return true;
}
@@ -523,6 +535,9 @@ public:
bool skipBubbleTail() const override {
return isBubbleBottom() && _caption.isEmpty();
}
bool canEditCaption() const override {
return !_data->isRoundVideo();
}
bool isReadyForOpen() const override {
return _data->loaded();
}
@@ -534,8 +549,15 @@ protected:
bool dataFinished() const override;
bool dataLoaded() const override;
void setClipReader(Media::Clip::ReaderPointer gif);
void clearClipReader() {
setClipReader(Media::Clip::ReaderPointer());
}
private:
DocumentData *_data;
QString mediaTypeString() const;
gsl::not_null<DocumentData*> _data;
int32 _thumbw = 1;
int32 _thumbh = 1;
Text _caption;
@@ -606,7 +628,7 @@ private:
int16 _pixw = 1;
int16 _pixh = 1;
ClickHandlerPtr _packLink;
DocumentData *_data;
gsl::not_null<DocumentData*> _data;
QString _emoji;
};
@@ -659,8 +681,7 @@ public:
}
private:
int32 _userId;
int32 _userId = 0;
UserData *_contact = nullptr;
int _phonew = 0;
@@ -671,6 +692,7 @@ private:
ClickHandlerPtr _linkl;
int _linkw = 0;
QString _link;
};
class HistoryWebPage : public HistoryMedia {
@@ -769,6 +791,7 @@ private:
int16 _pixw = 0;
int16 _pixh = 0;
};
class HistoryGame : public HistoryMedia {

View File

@@ -54,7 +54,10 @@ MediaOverviewType messageMediaToOverviewType(HistoryMedia *media) {
case MediaTypeFile: return OverviewFiles;
case MediaTypeMusicFile: return media->getDocument()->isMusic() ? OverviewMusicFiles : OverviewCount;
case MediaTypeVoiceFile: return OverviewVoiceFiles;
case MediaTypeGif: return media->getDocument()->isGifv() ? OverviewCount : OverviewFiles;
case MediaTypeGif: {
auto document = media->getDocument();
return (document->isGifv() || document->isRoundVideo()) ? OverviewCount : OverviewFiles;
} break;
default: break;
}
return OverviewCount;
@@ -434,8 +437,8 @@ MTPDmessage::Flags newForwardedFlags(PeerData *p, int32 from, HistoryMessage *fw
if (HistoryMedia *media = fwd->getMedia()) {
if (media->type() == MediaTypeVoiceFile) {
result |= MTPDmessage::Flag::f_media_unread;
// } else if (media->type() == MediaTypeVideo) {
// result |= MTPDmessage::flag_media_unread;
// } else if (media->type() == MediaTypeVideo) {
// result |= MTPDmessage::flag_media_unread;
}
}
}
@@ -2112,7 +2115,14 @@ HistoryService::PreparedText HistoryService::preparePinnedText() {
case MediaTypeVideo: return lang(lng_action_pinned_media_video);
case MediaTypeContact: return lang(lng_action_pinned_media_contact);
case MediaTypeFile: return lang(lng_action_pinned_media_file);
case MediaTypeGif: return lang(lng_action_pinned_media_gif);
case MediaTypeGif: {
if (auto document = media->getDocument()) {
if (document->isRoundVideo()) {
return lang(lng_action_pinned_media_video_message);
}
}
return lang(lng_action_pinned_media_gif);
} break;
case MediaTypeSticker: {
auto emoji = static_cast<HistorySticker*>(media)->emoji();
if (emoji.isEmpty()) {

View File

@@ -89,7 +89,11 @@ MTPVector<MTPDocumentAttribute> composeDocumentAttributes(DocumentData *document
if (document->dimensions.width() > 0 && document->dimensions.height() > 0) {
int32 duration = document->duration();
if (duration >= 0) {
attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height())));
auto flags = MTPDdocumentAttributeVideo::Flags(0);
if (document->isRoundVideo()) {
flags |= MTPDdocumentAttributeVideo::Flag::f_round_message;
}
attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(duration), MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height())));
} else {
attributes.push_back(MTP_documentAttributeImageSize(MTP_int(document->dimensions.width()), MTP_int(document->dimensions.height())));
}
@@ -928,6 +932,8 @@ void HistoryWidget::updateSendAction(History *history, SendAction::Type type, in
case Type::UploadVideo: action = MTP_sendMessageUploadVideoAction(MTP_int(progress)); break;
case Type::RecordVoice: action = MTP_sendMessageRecordAudioAction(); break;
case Type::UploadVoice: action = MTP_sendMessageUploadAudioAction(MTP_int(progress)); break;
case Type::RecordRound: action = MTP_sendMessageRecordRoundAction(); break;
case Type::UploadRound: action = MTP_sendMessageUploadRoundAction(); break;
case Type::UploadPhoto: action = MTP_sendMessageUploadPhotoAction(MTP_int(progress)); break;
case Type::UploadFile: action = MTP_sendMessageUploadDocumentAction(MTP_int(progress)); break;
case Type::ChooseLocation: action = MTP_sendMessageGeoLocationAction(); break;
@@ -1449,20 +1455,20 @@ void HistoryWidget::savedGifsGot(const MTPmessages_SavedGifs &gifs) {
if (gifs.type() != mtpc_messages_savedGifs) return;
auto &d = gifs.c_messages_savedGifs();
auto &d_gifs = d.vgifs.v;
auto &gifsList = d.vgifs.v;
SavedGifs &saved(cRefSavedGifs());
auto &saved = cRefSavedGifs();
saved.clear();
saved.reserve(d_gifs.size());
for (int32 i = 0, l = d_gifs.size(); i != l; ++i) {
DocumentData *doc = App::feedDocument(d_gifs.at(i));
if (!doc || !doc->isAnimation()) {
saved.reserve(gifsList.size());
for (auto &gif : gifsList) {
auto document = App::feedDocument(gif);
if (!document || !document->isGifv()) {
LOG(("API Error: bad document returned in HistoryWidget::savedGifsGot!"));
continue;
}
saved.push_back(doc);
saved.push_back(document);
}
if (Local::countSavedGifsHash() != d.vhash.v) {
LOG(("API Error: received saved gifs hash %1 while counted hash is %2").arg(d.vhash.v).arg(Local::countSavedGifsHash()));
@@ -3604,12 +3610,6 @@ void HistoryWidget::onCmdStart() {
setFieldText({ qsl("/"), TextWithTags::Tags() }, 0, Ui::FlatTextarea::AddToUndoHistory);
}
void HistoryWidget::contextMenuEvent(QContextMenuEvent *e) {
if (!_list) return;
return _list->showContextMenu(e);
}
void HistoryWidget::forwardMessage() {
auto item = App::contextItem();
if (!item || item->id < 0 || item->serviceMsg()) return;
@@ -3654,7 +3654,7 @@ QRect HistoryWidget::getMembersShowAreaGeometry() const {
int membersTextWidth = _titlePeerTextWidth;
int membersTextHeight = st::topBarHeight - membersTextTop;
return rtlrect(membersTextLeft, membersTextTop, membersTextWidth, membersTextHeight, width());
return myrtlrect(membersTextLeft, membersTextTop, membersTextWidth, membersTextHeight);
}
void HistoryWidget::setMembersShowAreaActive(bool active) {
@@ -5315,55 +5315,58 @@ void HistoryWidget::onEditMessage() {
auto to = App::contextItem();
if (!to) return;
if (EditCaptionBox::canEdit(to)) {
Ui::show(Box<EditCaptionBox>(to));
} else {
if (_recording) {
// Just fix some strange inconsistency.
_send->clearState();
if (auto media = to->getMedia()) {
if (media->canEditCaption()) {
Ui::show(Box<EditCaptionBox>(media, to->fullId()));
return;
}
if (!_editMsgId) {
if (_replyToId || !_field->isEmpty()) {
_history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled));
} else {
_history->clearLocalDraft();
}
}
auto original = to->originalText();
auto editText = textApplyEntities(original.text, original.entities);
auto editTags = ConvertEntitiesToTextTags(original.entities);
TextWithTags editData = { editText, editTags };
MessageCursor cursor = { editText.size(), editText.size(), QFIXED_MAX };
_history->setEditDraft(std::make_unique<Data::Draft>(editData, to->id, cursor, false));
applyDraft(false);
_previewData = nullptr;
if (auto media = to->getMedia()) {
if (media->type() == MediaTypeWebPage) {
_previewData = static_cast<HistoryWebPage*>(media)->webpage();
updatePreview();
}
}
if (!_previewData) {
onPreviewParse();
}
updateBotKeyboard();
if (!_field->isHidden()) _fieldBarCancel->show();
updateFieldPlaceholder();
updateMouseTracking();
updateReplyToName();
updateControlsGeometry();
updateField();
_saveDraftText = true;
_saveDraftStart = getms();
onDraftSave();
_field->setFocus();
}
if (_recording) {
// Just fix some strange inconsistency.
_send->clearState();
}
if (!_editMsgId) {
if (_replyToId || !_field->isEmpty()) {
_history->setLocalDraft(std::make_unique<Data::Draft>(_field, _replyToId, _previewCancelled));
} else {
_history->clearLocalDraft();
}
}
auto original = to->originalText();
auto editText = textApplyEntities(original.text, original.entities);
auto editTags = ConvertEntitiesToTextTags(original.entities);
TextWithTags editData = { editText, editTags };
MessageCursor cursor = { editText.size(), editText.size(), QFIXED_MAX };
_history->setEditDraft(std::make_unique<Data::Draft>(editData, to->id, cursor, false));
applyDraft(false);
_previewData = nullptr;
if (auto media = to->getMedia()) {
if (media->type() == MediaTypeWebPage) {
_previewData = static_cast<HistoryWebPage*>(media)->webpage();
updatePreview();
}
}
if (!_previewData) {
onPreviewParse();
}
updateBotKeyboard();
if (!_field->isHidden()) _fieldBarCancel->show();
updateFieldPlaceholder();
updateMouseTracking();
updateReplyToName();
updateControlsGeometry();
updateField();
_saveDraftText = true;
_saveDraftStart = getms();
onDraftSave();
_field->setFocus();
}
void HistoryWidget::onPinMessage() {
@@ -6093,7 +6096,7 @@ void HistoryWidget::paintEditHeader(Painter &p, const QRect &rect, int left, int
}
p.setFont(st::msgServiceNameFont);
p.drawTextLeft(left, top + st::msgReplyPadding.top() + st::msgServiceNameFont->ascent, width(), lang(lng_edit_message));
p.drawTextLeft(left, top + st::msgReplyPadding.top(), width(), lang(lng_edit_message));
if (!_replyEditMsg) return;
@@ -6163,7 +6166,7 @@ void HistoryWidget::drawPinnedBar(Painter &p) {
p.fillRect(myrtlrect(0, top, _chatWidth, st::historyReplyHeight), st::historyPinnedBg);
top += st::msgReplyPadding.top();
QRect rbar(rtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), top + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height(), _chatWidth));
QRect rbar(myrtlrect(st::msgReplyBarSkip + st::msgReplyBarPos.x(), top + st::msgReplyBarPos.y(), st::msgReplyBarSize.width(), st::msgReplyBarSize.height()));
p.fillRect(rbar, st::msgInReplyBarColor);
int32 left = st::msgReplyBarSkip + st::msgReplyBarSkip;

View File

@@ -363,7 +363,6 @@ protected:
void leaveEventHook(QEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
signals:
void cancelled();

View File

@@ -338,8 +338,8 @@ void Gif::clipCallback(Media::Clip::Notification notification) {
_gif.setBad();
getShownDocument()->forget();
} else if (_gif->ready() && !_gif->started()) {
int32 height = st::inlineMediaHeight;
QSize frame = countFrameSize();
auto height = st::inlineMediaHeight;
auto frame = countFrameSize();
_gif->start(frame.width(), frame.height(), _width, height, ImageRoundRadius::None, ImageRoundCorner::None);
} else if (_gif->autoPausedGif() && !context()->inlineItemVisible(this)) {
_gif.reset();

View File

@@ -335,14 +335,16 @@ void Result::createDocument() {
QString mime = _content_type;
QVector<MTPDocumentAttribute> attributes;
QSize dimensions(_width, _height);
auto dimensions = QSize(_width, _height);
if (_type == Type::Gif) {
const char *filename = (mime == qstr("video/mp4") ? "animation.gif.mp4" : "animation.gif");
auto filename = (mime == qstr("video/mp4") ? "animation.gif.mp4" : "animation.gif");
attributes.push_back(MTP_documentAttributeFilename(MTP_string(filename)));
attributes.push_back(MTP_documentAttributeAnimated());
attributes.push_back(MTP_documentAttributeVideo(MTP_int(_duration), MTP_int(_width), MTP_int(_height)));
auto flags = MTPDdocumentAttributeVideo::Flags(0);
attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(_duration), MTP_int(_width), MTP_int(_height)));
} else if (_type == Type::Video) {
attributes.push_back(MTP_documentAttributeVideo(MTP_int(_duration), MTP_int(_width), MTP_int(_height)));
auto flags = MTPDdocumentAttributeVideo::Flags(0);
attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(_duration), MTP_int(_width), MTP_int(_height)));
} else if (_type == Type::Audio) {
auto flags = MTPDdocumentAttributeAudio::Flags(0);
if (mime == qstr("audio/ogg")) {

View File

@@ -153,20 +153,12 @@ void Widget::historyMove(Direction direction) {
if (direction == Direction::Back || direction == Direction::Replace) {
delete base::take(wasStep);
}
if (getStep()->hasBack()) {
_back->showAnimated();
} else {
_back->hideAnimated();
}
if (getStep()->hasCover()) {
_settings->hideAnimated();
if (_update) _update->hideAnimated();
if (_changeLanguage) _changeLanguage->showAnimated();
} else {
_settings->showAnimated();
if (_update) _update->showAnimated();
if (_changeLanguage) _changeLanguage->hideAnimated();
}
_back->toggleAnimated(getStep()->hasBack());
auto stepHasCover = getStep()->hasCover();
_settings->toggleAnimated(!stepHasCover);
if (_update) _update->toggleAnimated(!stepHasCover);
if (_changeLanguage) _changeLanguage->toggleAnimated(stepHasCover);
_next->setText(getStep()->nextButtonText());
if (_resetAccount) _resetAccount->hideAnimated();
getStep()->showAnimated(direction);

View File

@@ -22,6 +22,35 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "langloaderplain.h"
LangString LangString::tag(ushort tag, const QString &replacement) {
for (const QChar *s = constData(), *ch = s, *e = ch + size(); ch != e;) {
if (*ch == TextCommand) {
if (ch + 3 < e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) {
if ((ch + 2)->unicode() == 0x0020 + tag) {
LangString result;
result.reserve(size() + replacement.size() - 4);
if (ch > s) result.append(midRef(0, ch - s));
result.append(replacement);
if (ch + 4 < e) result.append(midRef(ch - s + 4));
return result;
} else {
ch += 4;
}
} else {
const QChar *next = textSkipCommand(ch, e);
if (next == ch) {
++ch;
} else {
ch = next;
}
}
} else {
++ch;
}
}
return *this;
}
LangString langCounted(ushort key0, ushort tag, float64 value) { // current lang dependent
int v = qFloor(value);
QString sv;

View File

@@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#pragma once
#include "lang_auto.h"
constexpr const str_const LanguageCodes[] = {
"en",
"it",
@@ -31,53 +33,6 @@ constexpr const str_const LanguageCodes[] = {
};
constexpr const int languageTest = -1, languageDefault = 0, languageCount = base::array_size(LanguageCodes);
class LangString : public QString {
public:
LangString() {
}
LangString(const QString &str) : QString(str) {
}
LangString tag(ushort tag, const QString &replacement) {
for (const QChar *s = constData(), *ch = s, *e = ch + size(); ch != e;) {
if (*ch == TextCommand) {
if (ch + 3 < e && (ch + 1)->unicode() == TextCommandLangTag && *(ch + 3) == TextCommand) {
if ((ch + 2)->unicode() == 0x0020 + tag) {
LangString result;
result.reserve(size() + replacement.size() - 4);
if (ch > s) result.append(midRef(0, ch - s));
result.append(replacement);
if (ch + 4 < e) result.append(midRef(ch - s + 4));
return result;
} else {
ch += 4;
}
} else {
const QChar *next = textSkipCommand(ch, e);
if (next == ch) {
++ch;
} else {
ch = next;
}
}
} else {
++ch;
}
}
return *this;
}
LangString &operator=(const QString &str) {
QString::operator=(str);
return (*this);
}
};
LangString langCounted(ushort key0, ushort tag, float64 value);
#include "lang_auto.h"
const char *langKeyName(LangKey key);
template <typename WithYear, typename WithoutYear>
@@ -187,8 +142,8 @@ protected:
memset(_found, 0, sizeof(_found));
}
ushort tagIndex(const QByteArray &tag) const;
LangKey keyIndex(const QByteArray &key) const;
ushort tagIndex(QLatin1String tag) const;
LangKey keyIndex(QLatin1String key) const;
bool tagReplaced(LangKey key, ushort tag) const;
LangKey subkeyIndex(LangKey key, ushort tag, ushort index) const;

View File

@@ -33,22 +33,22 @@ bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) {
++from;
}
QByteArray varName = QByteArray(nameStart, from - nameStart);
auto varName = QLatin1String(nameStart, from - nameStart);
if (from == end || *from != '"') throw Exception(QString("Expected quote after key name '%1'!").arg(QLatin1String(varName)));
if (from == end || *from != '"') throw Exception(QString("Expected quote after key name '%1'!").arg(varName));
++from;
if (!skipWhitespaces(from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (*from != '=') throw Exception(QString("'=' expected in key '%1'!").arg(QLatin1String(varName)));
if (!skipWhitespaces(from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (*from != '=') throw Exception(QString("'=' expected in key '%1'!").arg(varName));
if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (*from != '"') throw Exception(QString("Expected string after '=' in key '%1'!").arg(QLatin1String(varName)));
if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (*from != '"') throw Exception(QString("Expected string after '=' in key '%1'!").arg(varName));
LangKey varKey = keyIndex(varName);
bool feedingValue = request.isEmpty();
if (feedingValue) {
if (varKey == lngkeys_cnt) {
warning(QString("Unknown key '%1'!").arg(QLatin1String(varName)));
warning(QString("Unknown key '%1'!").arg(varName));
}
} else if (!readingAll && !request.contains(varKey)) {
varKey = lngkeys_cnt;
@@ -60,10 +60,10 @@ bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) {
const char *start = ++from;
while (from < end && *from != '"') {
if (*from == '\n') {
throw Exception(QString("Unexpected end of string in key '%1'!").arg(QLatin1String(varName)));
throw Exception(QString("Unexpected end of string in key '%1'!").arg(varName));
}
if (*from == '\\') {
if (from + 1 >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (from + 1 >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{') {
if (readingValue && from > start) varValue.append(start, from - start);
start = ++from;
@@ -83,26 +83,26 @@ bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) {
}
if (from == tagStart) {
readingValue = false;
warning(QString("Expected tag name in key '%1'!").arg(QLatin1String(varName)));
warning(QString("Expected tag name in key '%1'!").arg(varName));
continue;
}
QByteArray tagName = QByteArray(tagStart, int(from - tagStart));
auto tagName = QLatin1String(tagStart, int(from - tagStart));
if (from == end || (*from != '}' && *from != ':')) throw Exception(QString("Expected '}' or ':' after tag name in key '%1'!").arg(QLatin1String(varName)));
if (from == end || (*from != '}' && *from != ':')) throw Exception(QString("Expected '}' or ':' after tag name in key '%1'!").arg(varName));
ushort index = tagIndex(tagName);
if (index == lngtags_cnt) {
readingValue = false;
warning(QString("Tag '%1' not found in key '%2', not using value.").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
warning(QString("Tag '%1' not found in key '%2', not using value.").arg(tagName).arg(varName));
continue;
}
if (!tagReplaced(varKey, index)) {
readingValue = false;
warning(QString("Unexpected tag '%1' in key '%2', not using value.").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
warning(QString("Unexpected tag '%1' in key '%2', not using value.").arg(tagName).arg(varName));
continue;
}
if (tagsUsed.contains(index)) throw Exception(QString("Tag '%1' double used in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (tagsUsed.contains(index)) throw Exception(QString("Tag '%1' double used in key '%2'!").arg(tagName).arg(varName));
tagsUsed.insert(index, true);
QString tagReplacer(4, TextCommand);
@@ -119,15 +119,15 @@ bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) {
while (from < end && *from != '"' && *from != '}') {
if (*from == '|') {
if (from > start) subvarValue.append(start, int(from - start));
if (countedIndex >= lngtags_max_counted_values) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (countedIndex >= lngtags_max_counted_values) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
LangKey subkey = subkeyIndex(varKey, index, countedIndex++);
if (subkey == lngkeys_cnt) {
readingValue = false;
warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName));
break;
} else {
if (feedingValue) {
if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName));
} else {
foundKeyValue(subkey);
}
@@ -137,10 +137,10 @@ bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) {
start = from + 1;
}
if (*from == '\n') {
throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
}
if (*from == '\\') {
if (from + 1 >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (from + 1 >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
if (*(from + 1) == '"' || *(from + 1) == '\\' || *(from + 1) == '{' || *(from + 1) == '#') {
if (from > start) subvarValue.append(start, int(from - start));
start = ++from;
@@ -152,9 +152,9 @@ bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) {
start = (++from) + 1;
}
} else if (*from == '{') {
throw Exception(QString("Unexpected tag inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
throw Exception(QString("Unexpected tag inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
} else if (*from == '#') {
if (foundtag) throw Exception(QString("Replacement '#' double used inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (foundtag) throw Exception(QString("Replacement '#' double used inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
foundtag = true;
if (from > start) subvarValue.append(start, int(from - start));
subvarValue.append(tagReplacer.toUtf8());
@@ -163,20 +163,20 @@ bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) {
++from;
}
if (!readingValue) continue;
if (from >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(QString::fromUtf8(tagName)).arg(QString::fromUtf8(varName)));
if (*from == '"') throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(QString::fromUtf8(tagName)).arg(QString::fromUtf8(varName)));
if (from >= end) throw Exception(QString("Unexpected end of file inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
if (*from == '"') throw Exception(QString("Unexpected end of string inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
if (from > start) subvarValue.append(start, int(from - start));
if (countedIndex >= lngtags_max_counted_values) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (countedIndex >= lngtags_max_counted_values) throw Exception(QString("Too many values inside counted tag '%1' in '%2' key!").arg(tagName).arg(varName));
LangKey subkey = subkeyIndex(varKey, index, countedIndex++);
if (subkey == lngkeys_cnt) {
readingValue = false;
warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
warning(QString("Unexpected counted tag '%1' in key '%2', not using value.").arg(tagName).arg(varName));
break;
} else {
if (feedingValue) {
if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(QLatin1String(tagName)).arg(QLatin1String(varName)));
if (!feedKeyValue(subkey, QString::fromUtf8(subvarValue))) throw Exception(QString("Tag '%1' is not counted in key '%2'!").arg(tagName).arg(varName));
} else {
foundKeyValue(subkey);
}
@@ -186,17 +186,17 @@ bool LangLoaderPlain::readKeyValue(const char *&from, const char *end) {
}
++from;
}
if (from >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (from >= end) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (readingValue && from > start) varValue.append(start, from - start);
if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(QLatin1String(varName)));
if (*from != ';') throw Exception(QString("';' expected after \"value\" in key '%1'!").arg(QLatin1String(varName)));
if (!skipWhitespaces(++from, end)) throw Exception(QString("Unexpected end of file in key '%1'!").arg(varName));
if (*from != ';') throw Exception(QString("';' expected after \"value\" in key '%1'!").arg(varName));
skipWhitespaces(++from, end);
if (readingValue) {
if (feedingValue) {
if (!feedKeyValue(varKey, QString::fromUtf8(varValue))) throw Exception(QString("Could not write value in key '%1'!").arg(QLatin1String(varName)));
if (!feedKeyValue(varKey, QString::fromUtf8(varValue))) throw Exception(QString("Could not write value in key '%1'!").arg(varName));
} else {
foundKeyValue(varKey);
result.insert(varKey, QString::fromUtf8(varValue));

View File

@@ -764,7 +764,7 @@ void MediaPreviewWidget::resizeEvent(QResizeEvent *e) {
}
void MediaPreviewWidget::showPreview(DocumentData *document) {
if (!document || (!document->isAnimation() && !document->sticker())) {
if (!document || (!document->isAnimation() && !document->sticker()) || document->isRoundVideo()) {
hidePreview();
return;
}

View File

@@ -3501,7 +3501,7 @@ void MainWidget::ptsApplySkippedUpdates() {
}
void MainWidget::feedDifference(const MTPVector<MTPUser> &users, const MTPVector<MTPChat> &chats, const MTPVector<MTPMessage> &msgs, const MTPVector<MTPUpdate> &other) {
App::wnd()->checkAutoLock();
AuthSession::Current().checkAutoLock();
App::feedUsers(users);
App::feedChats(chats);
feedMessageIds(other);
@@ -4016,7 +4016,9 @@ void MainWidget::updateNotifySetting(PeerData *peer, NotifySettingStatus notify,
}
if (peer->notify != EmptyNotifySettings && peer->notify != UnknownNotifySettings) {
if (notify != NotifySettingDontChange) {
peer->notify->sound = (notify == NotifySettingSetMuted) ? "" : "default";
if ((notify != NotifySettingSetMuted) && peer->notify->sound.isEmpty()) {
peer->notify->sound = qsl("default");
}
peer->notify->mute = (notify == NotifySettingSetMuted) ? (unixtime() + muteFor) : 0;
}
if (silent == SilentNotifiesSetSilent) {
@@ -4165,7 +4167,7 @@ MainWidget::~MainWidget() {
void MainWidget::updateOnline(bool gotOtherOffline) {
if (this != App::main()) return;
App::wnd()->checkAutoLock();
AuthSession::Current().checkAutoLock();
bool isOnline = App::wnd()->isActive();
int updateIn = Global::OnlineUpdatePeriod();
@@ -4260,7 +4262,7 @@ void MainWidget::checkIdleFinish() {
void MainWidget::updateReceived(const mtpPrime *from, const mtpPrime *end) {
if (end <= from) return;
App::wnd()->checkAutoLock();
AuthSession::Current().checkAutoLock();
if (mtpTypeId(*from) == mtpc_new_session_created) {
try {

View File

@@ -108,13 +108,12 @@ MainWindow::MainWindow() {
_inactiveTimer.setSingleShot(true);
connect(&_inactiveTimer, SIGNAL(timeout()), this, SLOT(onInactiveTimer()));
connect(&_autoLockTimer, SIGNAL(timeout()), this, SLOT(checkAutoLock()));
subscribe(Global::RefSelfChanged(), [this] { updateGlobalMenu(); });
subscribe(Window::Theme::Background(), [this](const Window::Theme::BackgroundUpdate &data) {
themeUpdated(data);
});
subscribe(Messenger::Instance().authSessionChanged(), [this] { checkAuthSession(); });
subscribe(Messenger::Instance().passcodedChanged(), [this] { updateGlobalMenu(); });
checkAuthSession();
setAttribute(Qt::WA_NoSystemBackground);
@@ -237,12 +236,8 @@ void MainWindow::clearPasscode() {
if (_intro) {
_intro->showAnimated(bg, true);
} else {
t_assert(_main != nullptr);
_main->showAnimated(bg, true);
}
AuthSession::Current().notifications().updateAll();
updateGlobalMenu();
if (_main) {
_main->checkStartUrl();
}
}
@@ -262,32 +257,6 @@ void MainWindow::setupPasscode() {
} else {
setInnerFocus();
}
_shouldLockAt = 0;
if (AuthSession::Exists()) {
AuthSession::Current().notifications().updateAll();
}
updateGlobalMenu();
}
void MainWindow::checkAutoLockIn(int msec) {
if (_autoLockTimer.isActive()) {
int remain = _autoLockTimer.remainingTime();
if (remain > 0 && remain <= msec) return;
}
_autoLockTimer.start(msec);
}
void MainWindow::checkAutoLock() {
if (!Global::LocalPasscode() || App::passcoded()) return;
App::app()->checkLocalTime();
auto ms = getms(true), idle = psIdleTime(), should = Global::AutoLock() * 1000LL;
if (idle >= should || (_shouldLockAt > 0 && ms > _shouldLockAt + 3000LL)) {
setupPasscode();
} else {
_shouldLockAt = ms + (should - idle);
_autoLockTimer.start(should - idle);
}
}
void MainWindow::setupIntro() {
@@ -348,7 +317,7 @@ void MainWindow::sendServiceHistoryRequest() {
UserData *user = App::userLoaded(ServiceUserId);
if (!user) {
auto userFlags = MTPDuser::Flag::f_first_name | MTPDuser::Flag::f_phone | MTPDuser::Flag::f_status | MTPDuser::Flag::f_verified;
user = App::feedUsers(MTP_vector<MTPUser>(1, MTP_user(MTP_flags(userFlags), MTP_int(ServiceUserId), MTPlong(), MTP_string("Telegram"), MTPstring(), MTPstring(), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently(), MTPint(), MTPstring(), MTPstring())));
user = App::feedUsers(MTP_vector<MTPUser>(1, MTP_user(MTP_flags(userFlags), MTP_int(ServiceUserId), MTPlong(), MTP_string("Telegram"), MTPstring(), MTPstring(), MTP_string("42777"), MTP_userProfilePhotoEmpty(), MTP_userStatusRecently(), MTPint(), MTPstring(), MTPstring(), MTPstring())));
}
_serviceHistoryRequest = MTP::send(MTPmessages_GetHistory(user->input, MTP_int(0), MTP_int(0), MTP_int(0), MTP_int(1), MTP_int(0), MTP_int(0)), _main->rpcDone(&MainWidget::serviceHistoryDone), _main->rpcFail(&MainWidget::serviceHistoryFail));
}
@@ -487,7 +456,9 @@ bool MainWindow::ui_isLayerShown() {
}
void MainWindow::ui_showMediaPreview(DocumentData *document) {
if (!document || ((!document->isAnimation() || !document->loaded()) && !document->sticker())) return;
if (!document || ((!document->isAnimation() || !document->loaded()) && !document->sticker())) {
return;
}
if (!_mediaPreview) {
_mediaPreview.create(bodyWidget(), controller());
updateControlsGeometry();

View File

@@ -90,7 +90,6 @@ public:
void setupPasscode();
void clearPasscode();
void checkAutoLockIn(int msec);
void setupIntro();
void setupMain(const MTPUser *user = nullptr);
void serviceNotification(const TextWithEntities &message, const MTPMessageMedia &media = MTP_messageMediaEmpty(), int32 date = 0, bool force = false);
@@ -164,8 +163,6 @@ protected:
void updateControlsGeometry() override;
public slots:
void checkAutoLock();
void showSettings();
void layerHidden();
void setInnerFocus();
@@ -238,9 +235,6 @@ private:
bool _inactivePress = false;
QTimer _inactiveTimer;
SingleTimer _autoLockTimer;
TimeMs _shouldLockAt = 0;
};
class PreLaunchWindow : public TWidget {

View File

@@ -72,6 +72,7 @@ namespace Media {
namespace Player {
namespace {
constexpr auto kVideoVolumeRound = 10000;
constexpr auto kPreloadSamples = 2LL * 48000; // preload next part if less than 2 seconds remains
constexpr auto kFadeDuration = TimeMs(500);
constexpr auto kCheckPlaybackPositionTimeout = TimeMs(100); // 100ms per check audio position
@@ -394,7 +395,7 @@ float64 ComputeVolume(AudioMsgId::Type type) {
switch (type) {
case AudioMsgId::Type::Voice: return suppressAllGain;
case AudioMsgId::Type::Song: return suppressSongGain * Global::SongVolume();
case AudioMsgId::Type::Video: return suppressSongGain * Global::VideoVolume();
case AudioMsgId::Type::Video: return suppressSongGain * mixer()->getVideoVolume();
}
return 1.;
}
@@ -553,7 +554,8 @@ void Mixer::Track::resetStream() {
}
Mixer::Mixer()
: _fader(new Fader(&_faderThread))
: _videoVolume(kVideoVolumeRound)
, _fader(new Fader(&_faderThread))
, _loader(new Loaders(&_loaderThread)) {
connect(this, SIGNAL(faderOnTimer()), _fader, SLOT(onTimer()), Qt::QueuedConnection);
connect(this, SIGNAL(suppressSong()), _fader, SLOT(onSuppressSong()));
@@ -1234,6 +1236,14 @@ void Mixer::reattachTracks() {
}
}
void Mixer::setVideoVolume(float64 volume) {
_videoVolume.storeRelease(qRound(volume * kVideoVolumeRound));
}
float64 Mixer::getVideoVolume() const {
return float64(_videoVolume.loadAcquire()) / kVideoVolumeRound;
}
Fader::Fader(QThread *thread) : QObject()
, _timer(this)
, _suppressAllGain(1., 1.)

View File

@@ -122,6 +122,10 @@ public:
void reattachIfNeeded();
void reattachTracks();
// Thread safe.
void setVideoVolume(float64 volume);
float64 getVideoVolume() const;
~Mixer();
private slots:
@@ -208,6 +212,7 @@ private:
Track _songTracks[kTogetherLimit];
Track _videoTrack;
QAtomicInt _videoVolume;
uint64 _lastVideoPlayId = 0;
TimeMs _lastVideoPlaybackWhen = 0;
TimeMs _lastVideoPlaybackCorrectedMs = 0;

View File

@@ -29,6 +29,7 @@ namespace Player {
Loaders::Loaders(QThread *thread) : _fromVideoNotify([this] { videoSoundAdded(); }) {
moveToThread(thread);
_fromVideoNotify.moveToThread(thread);
connect(thread, SIGNAL(started()), this, SLOT(onInit()));
connect(thread, SIGNAL(finished()), this, SLOT(deleteLater()));
}

View File

@@ -222,12 +222,15 @@ bool MediaView::fileBubbleShown() const {
bool MediaView::gifShown() const {
if (_gif && _gif->ready()) {
if (!_gif->started()) {
if (_doc->isVideo() && _autoplayVideoDocument != _doc && !_gif->videoPaused()) {
if (_doc && (_doc->isVideo() || _doc->isRoundVideo()) && _autoplayVideoDocument != _doc && !_gif->videoPaused()) {
_gif->pauseResumeVideo();
const_cast<MediaView*>(this)->_videoPaused = _gif->videoPaused();
}
_gif->start(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), ImageRoundRadius::None, ImageRoundCorner::None);
auto rounding = (_doc && _doc->isRoundVideo()) ? ImageRoundRadius::Ellipse : ImageRoundRadius::None;
_gif->start(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, ImageRoundCorner::All);
const_cast<MediaView*>(this)->_current = QPixmap();
updateMixerVideoVolume();
Global::RefVideoVolumeChanged().notify();
}
return true;// _gif->state() != Media::Clip::State::Error;
}
@@ -522,13 +525,13 @@ void MediaView::step_radial(TimeMs ms, bool timer) {
update(radialRect());
}
if (_doc && _doc->loaded() && _doc->size < App::kImageSizeLimit && (!_radial.animating() || _doc->isAnimation() || _doc->isVideo())) {
if (_doc->isVideo()) {
if (_doc->isVideo() || _doc->isRoundVideo()) {
_autoplayVideoDocument = _doc;
}
if (!_doc->data().isEmpty() && (_doc->isAnimation() || _doc->isVideo())) {
displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid));
} else {
const FileLocation &location(_doc->location(true));
auto &location = _doc->location(true);
if (location.accessEnable()) {
if (_doc->isAnimation() || _doc->isVideo() || _doc->isTheme() || QImageReader(location.name()).canRead()) {
displayDocument(_doc, App::histItemById(_msgmigrated ? 0 : _channel, _msgid));
@@ -646,6 +649,12 @@ void MediaView::showSaveMsgFile() {
File::ShowInFolder(_saveMsgFilename);
}
void MediaView::updateMixerVideoVolume() const {
if (_doc && (_doc->isVideo() || _doc->isRoundVideo())) {
Media::Player::mixer()->setVideoVolume(Global::VideoVolume());
}
}
void MediaView::close() {
if (_menu) _menu->hideMenu(true);
if (App::wnd()) {
@@ -828,7 +837,7 @@ void MediaView::clipCallback(Media::Clip::Notification notification) {
_videoStopped = true;
updateSilentVideoPlaybackState();
} else {
_videoIsSilent = _doc->isVideo() && !_gif->hasAudio();
_videoIsSilent = _doc && (_doc->isVideo() || _doc->isRoundVideo()) && !_gif->hasAudio();
_videoDurationMs = _gif->getDurationMs();
_videoPositionMs = _gif->getPositionMs();
if (_videoIsSilent) {
@@ -1140,7 +1149,7 @@ void MediaView::showDocument(DocumentData *doc, HistoryItem *context) {
_overview = doc->isVideo() ? OverviewVideos : OverviewFiles;
findCurrent();
}
if (doc->isVideo()) {
if (doc->isVideo() || doc->isRoundVideo()) {
_autoplayVideoDocument = doc;
}
displayDocument(doc, context);
@@ -1383,9 +1392,17 @@ void MediaView::displayFinished() {
}
}
Images::Options MediaView::videoThumbOptions() const {
auto options = Images::Option::Smooth | Images::Option::Blurred;
if (_doc && _doc->isRoundVideo()) {
options |= Images::Option::Circled;
}
return options;
}
void MediaView::initAnimation() {
t_assert(_doc != nullptr);
t_assert(_doc->isAnimation() || _doc->isVideo());
Expects(_doc != nullptr);
Expects(_doc->isAnimation() || _doc->isVideo());
auto &location = _doc->location(true);
if (!_doc->data().isEmpty()) {
@@ -1394,30 +1411,30 @@ void MediaView::initAnimation() {
createClipReader();
location.accessDisable();
} else if (_doc->dimensions.width() && _doc->dimensions.height()) {
int w = _doc->dimensions.width();
int h = _doc->dimensions.height();
_current = _doc->thumb->pixNoCache(w, h, Images::Option::Smooth | Images::Option::Blurred, w / cIntRetinaFactor(), h / cIntRetinaFactor());
auto w = _doc->dimensions.width();
auto h = _doc->dimensions.height();
_current = _doc->thumb->pixNoCache(w, h, videoThumbOptions(), w / cIntRetinaFactor(), h / cIntRetinaFactor());
if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
} else {
_current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), Images::Option::Smooth | Images::Option::Blurred, st::mediaviewFileIconSize, st::mediaviewFileIconSize);
_current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), videoThumbOptions(), st::mediaviewFileIconSize, st::mediaviewFileIconSize);
}
}
void MediaView::createClipReader() {
if (_gif) return;
t_assert(_doc != nullptr);
t_assert(_doc->isAnimation() || _doc->isVideo());
Expects(_doc != nullptr);
Expects(_doc->isAnimation() || _doc->isVideo());
if (_doc->dimensions.width() && _doc->dimensions.height()) {
int w = _doc->dimensions.width();
int h = _doc->dimensions.height();
_current = _doc->thumb->pixNoCache(w, h, Images::Option::Smooth | Images::Option::Blurred, w / cIntRetinaFactor(), h / cIntRetinaFactor());
_current = _doc->thumb->pixNoCache(w, h, videoThumbOptions(), w / cIntRetinaFactor(), h / cIntRetinaFactor());
if (cRetina()) _current.setDevicePixelRatio(cRetinaFactor());
} else {
_current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), Images::Option::Smooth | Images::Option::Blurred, st::mediaviewFileIconSize, st::mediaviewFileIconSize);
_current = _doc->thumb->pixNoCache(_doc->thumb->width(), _doc->thumb->height(), videoThumbOptions(), st::mediaviewFileIconSize, st::mediaviewFileIconSize);
}
auto mode = _doc->isVideo() ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif;
auto mode = (_doc->isVideo() || _doc->isRoundVideo()) ? Media::Clip::Reader::Mode::Video : Media::Clip::Reader::Mode::Gif;
_gif = std::make_unique<Media::Clip::Reader>(_doc->location(), _doc->data(), [this](Media::Clip::Notification notification) {
clipCallback(notification);
}, mode);
@@ -1475,7 +1492,8 @@ void MediaView::initThemePreview() {
}
void MediaView::createClipController() {
if (!_doc->isVideo()) return;
Expects(_doc != nullptr);
if (!_doc->isVideo() && !_doc->isRoundVideo()) return;
_clipController.create(this);
setClipControllerGeometry();
@@ -1530,7 +1548,8 @@ void MediaView::restartVideoAtSeekPosition(TimeMs positionMs) {
_autoplayVideoDocument = _doc;
if (_current.isNull()) {
_current = _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), ImageRoundRadius::None, ImageRoundCorner::None, getms());
auto rounding = (_doc && _doc->isRoundVideo()) ? ImageRoundRadius::Ellipse : ImageRoundRadius::None;
_current = _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, ImageRoundCorner::All, getms());
}
_gif = std::make_unique<Media::Clip::Reader>(_doc->location(), _doc->data(), [this](Media::Clip::Notification notification) {
clipCallback(notification);
@@ -1560,6 +1579,7 @@ void MediaView::onVideoSeekFinished(TimeMs positionMs) {
void MediaView::onVideoVolumeChanged(float64 volume) {
Global::SetVideoVolume(volume);
updateMixerVideoVolume();
Global::RefVideoVolumeChanged().notify();
}
@@ -1589,6 +1609,8 @@ void MediaView::onVideoPlayProgress(const AudioMsgId &audioId) {
if (state.duration) {
updateVideoPlaybackState(state);
}
AuthSession::Current().data().setLastTimeVideoPlayedAt(getms(true));
}
void MediaView::updateVideoPlaybackState(const Media::Player::TrackState &state) {
@@ -1638,6 +1660,9 @@ void MediaView::paintEvent(QPaintEvent *e) {
for (int i = 0, l = region.rectCount(); i < l; ++i) {
p.fillRect(rs.at(i), st::mediaviewVideoBg);
}
if (_doc && _doc->isRoundVideo()) {
p.setCompositionMode(m);
}
} else {
for (int i = 0, l = region.rectCount(); i < l; ++i) {
p.fillRect(rs.at(i), st::mediaviewBg);
@@ -1670,7 +1695,8 @@ void MediaView::paintEvent(QPaintEvent *e) {
if (_photo || fileShown()) {
QRect imgRect(_x, _y, _w, _h);
if (imgRect.intersects(r)) {
auto toDraw = _current.isNull() ? _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), ImageRoundRadius::None, ImageRoundCorner::None, ms) : _current;
auto rounding = (_doc && _doc->isRoundVideo()) ? ImageRoundRadius::Ellipse : ImageRoundRadius::None;
auto toDraw = _current.isNull() ? _gif->current(_gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), _gif->width() / cIntRetinaFactor(), _gif->height() / cIntRetinaFactor(), rounding, ImageRoundCorner::None, ms) : _current;
if (!_gif && (!_doc || !_doc->sticker() || _doc->sticker()->img->isNull()) && toDraw.hasAlpha()) {
p.fillRect(imgRect, _transparentBrush);
}
@@ -2011,7 +2037,7 @@ void MediaView::keyPressEvent(QKeyEvent *e) {
} else if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return || e->key() == Qt::Key_Space) {
if (_doc && !_doc->loading() && (fileBubbleShown() || !_doc->loaded())) {
onDocClick();
} else if (_doc && _doc->isVideo()) {
} else if (_doc && (_doc->isVideo() || _doc->isRoundVideo())) {
onVideoPauseResume();
}
} else if (e->key() == Qt::Key_Left) {
@@ -2463,7 +2489,7 @@ void MediaView::updateOver(QPoint pos) {
} else if (_closeNav.contains(pos)) {
updateOverState(OverClose);
} else if (_doc && fileShown() && QRect(_x, _y, _w, _h).contains(pos)) {
if (_doc->isVideo() && _gif) {
if ((_doc->isVideo() || _doc->isRoundVideo()) && _gif) {
updateOverState(OverVideo);
} else if (!_doc->loaded()) {
updateOverState(OverIcon);

View File

@@ -154,6 +154,7 @@ private:
};
void showSaveMsgFile();
void updateMixerVideoVolume() const;
void dropdownHidden();
void updateDocSize();
@@ -178,6 +179,7 @@ private:
void initAnimation();
void createClipReader();
Images::Options videoThumbOptions() const;
void initThemePreview();
void destroyThemePreview();

View File

@@ -129,7 +129,7 @@ Messenger::Messenger() : QObject()
DEBUG_LOG(("Application Info: showing."));
if (state == Local::ReadMapPassNeeded) {
_window->setupPasscode();
setupPasscode();
} else {
if (AuthSession::Exists()) {
_window->setupMain();
@@ -386,7 +386,8 @@ void Messenger::loadLanguage() {
LOG(("Lang load warnings: %1").arg(loader.warnings()));
}
}
QCoreApplication::instance()->installTranslator(_translator = new Translator());
_translator = std::make_unique<Translator>();
QCoreApplication::instance()->installTranslator(_translator.get());
}
void Messenger::startLocalStorage() {
@@ -480,17 +481,17 @@ void Messenger::peerClearPhoto(PeerId id) {
}
}
void Messenger::killDownloadSessionsStart(int32 dc) {
if (killDownloadSessionTimes.constFind(dc) == killDownloadSessionTimes.cend()) {
killDownloadSessionTimes.insert(dc, getms() + MTPAckSendWaiting + MTPKillFileSessionTimeout);
void Messenger::killDownloadSessionsStart(MTP::DcId dcId) {
if (killDownloadSessionTimes.constFind(dcId) == killDownloadSessionTimes.cend()) {
killDownloadSessionTimes.insert(dcId, getms() + MTPAckSendWaiting + MTPKillFileSessionTimeout);
}
if (!killDownloadSessionsTimer.isActive()) {
killDownloadSessionsTimer.start(MTPAckSendWaiting + MTPKillFileSessionTimeout + 5);
}
}
void Messenger::killDownloadSessionsStop(int32 dc) {
killDownloadSessionTimes.remove(dc);
void Messenger::killDownloadSessionsStop(MTP::DcId dcId) {
killDownloadSessionTimes.remove(dcId);
if (killDownloadSessionTimes.isEmpty() && killDownloadSessionsTimer.isActive()) {
killDownloadSessionsTimer.stop();
}
@@ -542,7 +543,7 @@ void Messenger::killDownloadSessions() {
auto ms = getms(), left = static_cast<TimeMs>(MTPAckSendWaiting) + MTPKillFileSessionTimeout;
for (auto i = killDownloadSessionTimes.begin(); i != killDownloadSessionTimes.end(); ) {
if (i.value() <= ms) {
for (int j = 0; j < MTPDownloadSessionsCount; ++j) {
for (int j = 0; j < MTP::kDownloadSessionsCount; ++j) {
MTP::stopSession(MTP::downloadDcId(i.key(), j));
}
i = killDownloadSessionTimes.erase(i);
@@ -727,6 +728,17 @@ void Messenger::checkMapVersion() {
}
}
void Messenger::setupPasscode() {
_window->setupPasscode();
_passcodedChanged.notify();
}
void Messenger::clearPasscode() {
cSetPasscodeBadTries(0);
_window->clearPasscode();
_passcodedChanged.notify();
}
void Messenger::prepareToDestroy() {
_window.reset();
@@ -751,7 +763,6 @@ Messenger::~Messenger() {
deinitLocationManager();
delete base::take(_uploader);
delete base::take(_translator);
Window::Theme::Unload();

View File

@@ -108,11 +108,16 @@ public:
void writeUserConfigIn(TimeMs ms);
void killDownloadSessionsStart(int32 dc);
void killDownloadSessionsStop(int32 dc);
void killDownloadSessionsStart(MTP::DcId dcId);
void killDownloadSessionsStop(MTP::DcId dcId);
void checkLocalTime();
void checkMapVersion();
void setupPasscode();
void clearPasscode();
base::Observable<void> &passcodedChanged() {
return _passcodedChanged;
}
void handleAppActivated();
void handleAppDeactivated();
@@ -145,7 +150,7 @@ private:
QMap<FullMsgId, PeerId> photoUpdates;
QMap<int32, TimeMs> killDownloadSessionTimes;
QMap<MTP::DcId, TimeMs> killDownloadSessionTimes;
SingleTimer killDownloadSessionsTimer;
// Some fields are just moved from the declaration.
@@ -154,12 +159,13 @@ private:
std::unique_ptr<MainWindow> _window;
FileUploader *_uploader = nullptr;
Translator *_translator = nullptr;
std::unique_ptr<Translator> _translator;
std::unique_ptr<MTP::DcOptions> _dcOptions;
std::unique_ptr<MTP::Instance> _mtproto;
std::unique_ptr<MTP::Instance> _mtprotoForKeysDestroy;
std::unique_ptr<AuthSession> _authSession;
base::Observable<void> _authSessionChanged;
base::Observable<void> _passcodedChanged;
};

View File

@@ -340,34 +340,13 @@ private:
};
typedef QMap<uint64, RSAPublicKey> RSAPublicKeys;
RSAPublicKeys InitRSAPublicKeys() {
DEBUG_LOG(("MTP Info: RSA public keys list creation"));
RSAPublicKeys result;
int keysCount;
const char **keys = cPublicRSAKeys(keysCount);
for (int i = 0; i < keysCount; ++i) {
RSAPublicKey key(keys[i]);
if (key.isValid()) {
result.insert(key.getFingerPrint(), key);
} else {
LOG(("MTP Error: could not read this public RSA key:"));
LOG((keys[i]));
}
}
DEBUG_LOG(("MTP Info: read %1 public RSA keys").arg(result.size()));
return result;
}
} // namespace
Connection::Connection(Instance *instance) : _instance(instance) {
}
void Connection::start(SessionData *sessionData, ShiftedDcId shiftedDcId) {
t_assert(thread == nullptr && data == nullptr);
Expects(thread == nullptr && data == nullptr);
thread = std::make_unique<Thread>();
auto newData = std::make_unique<ConnectionPrivate>(_instance, thread.get(), this, sessionData, shiftedDcId);
@@ -378,14 +357,14 @@ void Connection::start(SessionData *sessionData, ShiftedDcId shiftedDcId) {
}
void Connection::kill() {
t_assert(data != nullptr && thread != nullptr);
Expects(data != nullptr && thread != nullptr);
data->stop();
data = nullptr;
thread->quit();
}
void Connection::waitTillFinish() {
t_assert(data == nullptr && thread != nullptr);
Expects(data == nullptr && thread != nullptr);
DEBUG_LOG(("Waiting for connectionThread to finish"));
thread->wait();
@@ -393,19 +372,19 @@ void Connection::waitTillFinish() {
}
int32 Connection::state() const {
t_assert(data != nullptr && thread != nullptr);
Expects(data != nullptr && thread != nullptr);
return data->getState();
}
QString Connection::transport() const {
t_assert(data != nullptr && thread != nullptr);
Expects(data != nullptr && thread != nullptr);
return data->transport();
}
Connection::~Connection() {
t_assert(data == nullptr);
Expects(data == nullptr);
if (thread) {
waitTillFinish();
}
@@ -477,10 +456,10 @@ ConnectionPrivate::ConnectionPrivate(Instance *instance, QThread *thread, Connec
retryTimer.moveToThread(thread);
moveToThread(thread);
t_assert(_shiftedDcId != 0);
Expects(_shiftedDcId != 0);
connect(thread, SIGNAL(started()), this, SLOT(socketStart()));
connect(thread, SIGNAL(finished()), this, SLOT(doFinish()));
connect(thread, &QThread::started, this, [this] { connectToServer(); });
connect(thread, &QThread::finished, this, [this] { finishAndDestroy(); });
connect(this, SIGNAL(finished(internal::Connection*)), _instance, SLOT(connectionFinished(internal::Connection*)), Qt::QueuedConnection);
connect(&retryTimer, SIGNAL(timeout()), this, SLOT(retryByTimer()));
@@ -515,7 +494,11 @@ ConnectionPrivate::ConnectionPrivate(Instance *instance, QThread *thread, Connec
}
void ConnectionPrivate::onConfigLoaded() {
socketStart(true);
connectToServer(true);
}
void ConnectionPrivate::onCDNConfigLoaded() {
restart();
}
int32 ConnectionPrivate::getShiftedDcId() const {
@@ -881,12 +864,14 @@ void ConnectionPrivate::tryToSend() {
}
}
MTPInitConnection<mtpRequest> initWrapperImpl, *initWrapper = &initWrapperImpl;
MTPInitConnection<mtpRequest> initWrapper;
int32 initSize = 0, initSizeInInts = 0;
if (needsLayer) {
auto langCode = (cLang() == languageTest || cLang() == languageDefault) ? Sandbox::LangSystemISO() : str_const_toString(LanguageCodes[cLang()]);
initWrapperImpl = MTPInitConnection<mtpRequest>(MTP_int(ApiId), MTP_string(cApiDeviceModel()), MTP_string(cApiSystemVersion()), MTP_string(cApiAppVersion()), MTP_string(langCode), mtpRequest());
initSizeInInts = (initWrapper->innerLength() >> 2) + 2;
auto deviceModel = (_dcType == DcType::Cdn) ? "n/a" : cApiDeviceModel();
auto systemVersion = (_dcType == DcType::Cdn) ? "n/a" : cApiSystemVersion();
initWrapper = MTPInitConnection<mtpRequest>(MTP_int(ApiId), MTP_string(deviceModel), MTP_string(systemVersion), MTP_string(cApiAppVersion()), MTP_string(langCode), mtpRequest());
initSizeInInts = (initWrapper.innerLength() >> 2) + 2;
initSize = initSizeInInts * sizeof(mtpPrime);
}
@@ -946,7 +931,7 @@ void ConnectionPrivate::tryToSend() {
memcpy(wrappedRequest->data(), toSendRequest->constData(), 7 * sizeof(mtpPrime)); // all except length
wrappedRequest->push_back(mtpc_invokeWithLayer);
wrappedRequest->push_back(MTP::internal::CurrentLayer);
initWrapper->write(*wrappedRequest);
initWrapper.write(*wrappedRequest);
wrappedRequest->resize(wrappedRequest->size() + noWrapSize);
memcpy(wrappedRequest->data() + wrappedRequest->size() - noWrapSize, toSendRequest->constData() + 8, noWrapSize * sizeof(mtpPrime));
toSendRequest = wrappedRequest;
@@ -978,7 +963,7 @@ void ConnectionPrivate::tryToSend() {
initSerialized.reserve(initSizeInInts);
initSerialized.push_back(mtpc_invokeWithLayer);
initSerialized.push_back(MTP::internal::CurrentLayer);
initWrapper->write(initSerialized);
initWrapper.write(initSerialized);
}
toSendRequest = mtpRequestData::prepare(containerSize, containerSize + 3 * toSend.size()); // prepare container + each in invoke after
toSendRequest->push_back(mtpc_msg_container);
@@ -1082,7 +1067,7 @@ void ConnectionPrivate::retryByTimer() {
}
keyId = 0;
}
socketStart();
connectToServer();
}
void ConnectionPrivate::restartNow() {
@@ -1091,27 +1076,31 @@ void ConnectionPrivate::restartNow() {
restart();
}
void ConnectionPrivate::socketStart(bool afterConfig) {
void ConnectionPrivate::connectToServer(bool afterConfig) {
if (_finished) {
DEBUG_LOG(("MTP Error: socketStart() called for finished connection!"));
DEBUG_LOG(("MTP Error: connectToServer() called for finished connection!"));
return;
}
auto dcType = DcOptions::DcType::Regular;
auto isDownloadDc = isDownloadDcId(_shiftedDcId);
if (isDownloadDc) { // using media_only addresses only if key for this dc is already created
auto bareDc = bareDcId(_shiftedDcId);
_dcType = Messenger::Instance().dcOptions()->dcType(_shiftedDcId);
if (_dcType == DcType::MediaDownload) { // using media_only addresses only if key for this dc is already created
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData || sessionData->getKey()) {
dcType = DcOptions::DcType::MediaDownload;
if (!sessionData || !sessionData->getKey()) {
_dcType = DcType::Regular;
}
} else if (_dcType == DcType::Cdn && !_instance->isKeysDestroyer()) {
if (!Messenger::Instance().dcOptions()->hasCDNKeysForDc(bareDc)) {
requestCDNConfig();
return;
}
}
auto bareDc = bareDcId(_shiftedDcId);
using Variants = DcOptions::Variants;
auto kIPv4 = Variants::IPv4;
auto kIPv6 = Variants::IPv6;
auto kTcp = Variants::Tcp;
auto kHttp = Variants::Http;
auto variants = Messenger::Instance().dcOptions()->lookup(bareDc, dcType);
auto variants = Messenger::Instance().dcOptions()->lookup(bareDc, _dcType);
auto noIPv4 = (variants.data[kIPv4][kHttp].port == 0);
auto noIPv6 = (!Global::TryIPv6() || (variants.data[kIPv6][kHttp].port == 0));
if (noIPv4 && noIPv6) {
@@ -1128,7 +1117,7 @@ void ConnectionPrivate::socketStart(bool afterConfig) {
DEBUG_LOG(("MTP Info: DC %1 options for IPv4 over HTTP not found, waiting for config").arg(_shiftedDcId));
if (Global::TryIPv6() && noIPv6) DEBUG_LOG(("MTP Info: DC %1 options for IPv6 over HTTP not found, waiting for config").arg(_shiftedDcId));
connect(_instance, SIGNAL(configLoaded()), this, SLOT(onConfigLoaded()), Qt::UniqueConnection);
QMetaObject::invokeMethod(_instance, "configLoadRequest", Qt::QueuedConnection);
InvokeQueued(_instance, [instance = _instance] { instance->configLoadRequest(); });
return;
}
@@ -1201,18 +1190,18 @@ void ConnectionPrivate::restart() {
void ConnectionPrivate::onSentSome(uint64 size) {
if (!_waitForReceivedTimer.isActive()) {
uint64 remain = _waitForReceived;
auto remain = static_cast<uint64>(_waitForReceived);
if (!oldConnection) {
uint64 remainBySize = size * _waitForReceived / 8192; // 8kb / sec, so 512 kb give 64 sec
auto remainBySize = size * _waitForReceived / 8192; // 8kb / sec, so 512 kb give 64 sec
remain = snap(remainBySize, remain, uint64(MTPMaxReceiveDelay));
if (remain != _waitForReceived) {
DEBUG_LOG(("Checking connect for request with size %1 bytes, delay will be %2").arg(size).arg(remain));
}
}
if (isUploadDcId(_shiftedDcId)) {
remain *= MTPUploadSessionsCount;
remain *= kUploadSessionsCount;
} else if (isDownloadDcId(_shiftedDcId)) {
remain *= MTPDownloadSessionsCount;
remain *= kDownloadSessionsCount;
}
_waitForReceivedTimer.start(remain);
}
@@ -1276,7 +1265,7 @@ void ConnectionPrivate::onWaitReceivedFailed() {
if (retryTimer.isActive()) return;
DEBUG_LOG(("MTP Info: immediate restart!"));
QTimer::singleShot(0, this, SLOT(socketStart()));
InvokeQueued(this, [this] { connectToServer(); });
}
void ConnectionPrivate::onWaitConnectedFailed() {
@@ -1287,7 +1276,7 @@ void ConnectionPrivate::onWaitConnectedFailed() {
restarted = true;
DEBUG_LOG(("MTP Info: immediate restart!"));
QTimer::singleShot(0, this, SLOT(socketStart()));
InvokeQueued(this, [this] { connectToServer(); });
}
void ConnectionPrivate::onWaitIPv4Failed() {
@@ -1319,13 +1308,18 @@ void ConnectionPrivate::doDisconnect() {
restarted = false;
}
void ConnectionPrivate::doFinish() {
void ConnectionPrivate::finishAndDestroy() {
doDisconnect();
_finished = true;
emit finished(_owner);
deleteLater();
}
void ConnectionPrivate::requestCDNConfig() {
connect(_instance, SIGNAL(cdnConfigLoaded()), this, SLOT(onCDNConfigLoaded()), Qt::UniqueConnection);
InvokeQueued(_instance, [instance = _instance] { instance->cdnConfigLoadRequest(); });
}
void ConnectionPrivate::handleReceived() {
QReadLocker lockFinished(&sessionDataMutex);
if (!sessionData) return;
@@ -2046,16 +2040,20 @@ ConnectionPrivate::HandleResult ConnectionPrivate::handleOneReceived(const mtpPr
return HandleResult::ResetSession;
}
mtpBuffer update(end - from);
if (end > from) memcpy(update.data(), from, (end - from) * sizeof(mtpPrime));
if (_dcType == DcType::Regular) {
mtpBuffer update(end - from);
if (end > from) memcpy(update.data(), from, (end - from) * sizeof(mtpPrime));
QWriteLocker locker(sessionData->haveReceivedMutex());
mtpResponseMap &haveReceived(sessionData->haveReceivedMap());
mtpRequestId fakeRequestId = sessionData->nextFakeRequestId();
haveReceived.insert(fakeRequestId, mtpResponse(update)); // notify main process about new updates
QWriteLocker locker(sessionData->haveReceivedMutex());
mtpResponseMap &haveReceived(sessionData->haveReceivedMap());
mtpRequestId fakeRequestId = sessionData->nextFakeRequestId();
haveReceived.insert(fakeRequestId, mtpResponse(update)); // notify main process about new updates
if (cons != mtpc_updatesTooLong && cons != mtpc_updateShortMessage && cons != mtpc_updateShortChatMessage && cons != mtpc_updateShortSentMessage && cons != mtpc_updateShort && cons != mtpc_updatesCombined && cons != mtpc_updates) {
LOG(("Message Error: unknown constructor %1").arg(cons)); // maybe new api?..
if (cons != mtpc_updatesTooLong && cons != mtpc_updateShortMessage && cons != mtpc_updateShortChatMessage && cons != mtpc_updateShortSentMessage && cons != mtpc_updateShort && cons != mtpc_updatesCombined && cons != mtpc_updates) {
LOG(("Message Error: unknown constructor %1").arg(cons)); // maybe new api?..
}
} else {
LOG(("Message Error: unexpected updates in dcType: %1").arg(static_cast<int>(_dcType)));
}
return HandleResult::Success;
@@ -2430,27 +2428,17 @@ void ConnectionPrivate::pqAnswered() {
return restart();
}
static MTP::internal::RSAPublicKeys RSAKeys = MTP::internal::InitRSAPublicKeys();
const MTP::internal::RSAPublicKey *rsaKey = nullptr;
auto &fingerPrints = res_pq.c_resPQ().vserver_public_key_fingerprints.v;
for (auto &fingerPrint : fingerPrints) {
auto it = RSAKeys.constFind(static_cast<uint64>(fingerPrint.v));
if (it != RSAKeys.cend()) {
rsaKey = &it.value();
break;
auto rsaKey = internal::RSAPublicKey();
if (!Messenger::Instance().dcOptions()->getDcRSAKey(bareDcId(_shiftedDcId), res_pq.c_resPQ().vserver_public_key_fingerprints.v, &rsaKey)) {
if (_dcType == DcType::Cdn) {
LOG(("Warning: CDN public RSA key not found"));
requestCDNConfig();
return;
}
}
if (!rsaKey) {
QStringList suggested, my;
for (auto &fingerPrint : fingerPrints) {
suggested.push_back(QString("%1").arg(fingerPrint.v));
}
for (auto i = RSAKeys.cbegin(), e = RSAKeys.cend(); i != e; ++i) {
my.push_back(QString("%1").arg(i.key()));
}
LOG(("AuthKey Error: could not choose public RSA key, suggested fingerprints: %1, my fingerprints: %2").arg(suggested.join(", ")).arg(my.join(", ")));
LOG(("AuthKey Error: could not choose public RSA key"));
return restart();
}
t_assert(rsaKey.isValid());
_authKeyData->server_nonce = res_pq_data.vserver_nonce;
_authKeyData->new_nonce = rand_value<MTPint256>();
@@ -2477,14 +2465,14 @@ void ConnectionPrivate::pqAnswered() {
MTPReq_DH_params req_DH_params;
req_DH_params.vnonce = _authKeyData->nonce;
req_DH_params.vserver_nonce = _authKeyData->server_nonce;
req_DH_params.vpublic_key_fingerprint = MTP_long(rsaKey->getFingerPrint());
req_DH_params.vpublic_key_fingerprint = MTP_long(rsaKey.getFingerPrint());
req_DH_params.vp = p_q_inner.c_p_q_inner_data().vp;
req_DH_params.vq = p_q_inner.c_p_q_inner_data().vq;
req_DH_params.vencrypted_data = MTP_string(std::move(dhEncString));
sendRequestNotSecure(req_DH_params);
}
std::string ConnectionPrivate::encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey *key) {
std::string ConnectionPrivate::encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey &key) {
auto p_q_inner_size = data.innerLength();
auto encSize = (p_q_inner_size >> 2) + 6;
if (encSize >= 65) {
@@ -2509,7 +2497,7 @@ std::string ConnectionPrivate::encryptPQInnerRSA(const MTPP_Q_inner_data &data,
}
auto dhEncString = std::string();
if (!key->encrypt(reinterpret_cast<const char*>(&encBuffer[0]) + 3, dhEncString)) {
if (!key.encrypt(reinterpret_cast<const char*>(&encBuffer[0]) + 3, dhEncString)) {
return std::string();
}
return dhEncString;
@@ -2872,17 +2860,7 @@ void ConnectionPrivate::onError4(qint32 errorCode) {
LOG(("Protocol Error: -429 flood code returned!"));
}
if (_conn || !_conn6) {
destroyConn();
_waitForConnectedTimer.stop();
if (errorCode == -404 && _instance->isKeysDestroyer()) {
LOG(("MTP Info: -404 error received on destroying key %1, assuming it is destroyed.").arg(_shiftedDcId));
emit _instance->keyDestroyed(_shiftedDcId);
return;
} else {
MTP_LOG(_shiftedDcId, ("Restarting after error in IPv4 connection, error code: %1...").arg(errorCode));
return restart();
}
handleError(errorCode);
} else {
destroyConn(&_conn4);
}
@@ -2895,22 +2873,32 @@ void ConnectionPrivate::onError6(qint32 errorCode) {
LOG(("Protocol Error: -429 flood code returned!"));
}
if (_conn || !_conn4) {
destroyConn();
_waitForConnectedTimer.stop();
if (errorCode == -404 && _instance->isKeysDestroyer()) {
LOG(("MTP Info: -404 error received on destroying key %1, assuming it is destroyed.").arg(_shiftedDcId));
emit _instance->keyDestroyed(_shiftedDcId);
return;
} else {
MTP_LOG(_shiftedDcId, ("Restarting after error in IPv6 connection, error code: %1...").arg(errorCode));
return restart();
}
handleError(errorCode);
} else {
destroyConn(&_conn6);
}
}
void ConnectionPrivate::handleError(int errorCode) {
destroyConn();
_waitForConnectedTimer.stop();
if (errorCode == -404) {
if (_instance->isKeysDestroyer()) {
LOG(("MTP Info: -404 error received on destroying key %1, assuming it is destroyed.").arg(_shiftedDcId));
emit _instance->keyDestroyed(_shiftedDcId);
return;
} else if (_dcType == DcType::Cdn) {
LOG(("MTP Info: -404 error received in CDN dc %1, assuming it was destroyed, recreating.").arg(_shiftedDcId));
clearMessages();
keyId = kRecreateKeyId;
return restart();
}
}
MTP_LOG(_shiftedDcId, ("Restarting after error in connection, error code: %1...").arg(errorCode));
return restart();
}
void ConnectionPrivate::onReadyData() {
}

View File

@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mtproto/core_types.h"
#include "mtproto/auth_key.h"
#include "mtproto/dc_options.h"
#include "core/single_timer.h"
namespace MTP {
@@ -128,7 +129,6 @@ public slots:
void onReceivedSome();
void onReadyData();
void socketStart(bool afterConfig = false);
void onConnected4();
void onConnected6();
@@ -137,8 +137,6 @@ public slots:
void onError4(qint32 errorCode);
void onError6(qint32 errorCode);
void doFinish();
// Auth key creation packet receive slots
void pqAnswered();
void dhParamsAnswered();
@@ -153,10 +151,15 @@ public slots:
void updateAuthKey();
void onConfigLoaded();
void onCDNConfigLoaded();
private:
void connectToServer(bool afterConfig = false);
void doDisconnect();
void restart();
void finishAndDestroy();
void requestCDNConfig();
void handleError(int errorCode);
void createConn(bool createIPv4, bool createIPv6);
void destroyConn(AbstractConnection **conn = 0); // 0 - destory all
@@ -182,10 +185,11 @@ private:
bool setState(int32 state, int32 ifState = Connection::UpdateAlways);
std::string encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey *key);
std::string encryptPQInnerRSA(const MTPP_Q_inner_data &data, const MTP::internal::RSAPublicKey &key);
std::string encryptClientDHInner(const MTPClient_DH_Inner_Data &data);
Instance *_instance = nullptr;
DcType _dcType = DcType::Regular;
mutable QReadWriteLock stateConnMutex;
int32 _state = DisconnectedState;

View File

@@ -20,12 +20,55 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "mtproto/dc_options.h"
#include "storage/serialize_common.h"
namespace MTP {
class DcOptions::WriteLocker {
public:
WriteLocker(DcOptions *that) : _that(that), _lock(&_that->_useThroughLockers) {
}
~WriteLocker() {
_that->computeCdnDcIds();
}
private:
gsl::not_null<DcOptions*> _that;
QWriteLocker _lock;
};
class DcOptions::ReadLocker {
public:
ReadLocker(const DcOptions *that) : _lock(&that->_useThroughLockers) {
}
private:
QReadLocker _lock;
};
void DcOptions::readBuiltInPublicKeys() {
auto keysCount = 0;
auto keys = cPublicRSAKeys(keysCount);
for (auto i = 0; i != keysCount; ++i) {
auto keyBytes = gsl::as_bytes(gsl::make_span(keys[i], keys[i] + strlen(keys[i])));
auto key = internal::RSAPublicKey(keyBytes);
if (key.isValid()) {
_publicKeys.emplace(key.getFingerPrint(), std::move(key));
} else {
LOG(("MTP Error: could not read this public RSA key:"));
LOG((keys[i]));
}
}
}
void DcOptions::constructFromBuiltIn() {
QWriteLocker lock(&_mutex);
WriteLocker lock(this);
_data.clear();
readBuiltInPublicKeys();
auto bdcs = builtInDcs();
for (auto i = 0, l = builtInDcsCount(); i != l; ++i) {
auto flags = MTPDdcOption::Flags(0);
@@ -53,7 +96,7 @@ void DcOptions::processFromList(const QVector<MTPDcOption> &options, bool overwr
auto shiftedIdsProcessed = std::vector<ShiftedDcId>();
shiftedIdsProcessed.reserve(options.size());
{
QWriteLocker lock(&_mutex);
WriteLocker lock(this);
if (overwrite) {
idsChanged.reserve(_data.size());
}
@@ -107,22 +150,22 @@ void DcOptions::addFromList(const MTPVector<MTPDcOption> &options) {
processFromList(options.v, false);
}
void DcOptions::addFromOther(const DcOptions &options) {
void DcOptions::addFromOther(DcOptions &&options) {
if (this == &options || _immutable) {
return;
}
auto idsChanged = std::vector<DcId>();
{
QReadLocker lock(&options._mutex);
ReadLocker lock(&options);
if (options._data.empty()) {
return;
}
idsChanged.reserve(options._data.size());
{
QWriteLocker lock(&_mutex);
for (auto &item : options._data) {
WriteLocker lock(this);
for (auto &item : base::take(options._data)) {
auto dcId = item.second.id;
auto flags = item.second.flags;
auto &ip = item.second.ip;
@@ -133,6 +176,11 @@ void DcOptions::addFromOther(const DcOptions &options) {
}
}
}
for (auto &keysForDc : options._cdnPublicKeys) {
for (auto &entry : keysForDc.second) {
_cdnPublicKeys[keysForDc.first].insert(std::move(entry));
}
}
}
}
@@ -142,7 +190,7 @@ void DcOptions::addFromOther(const DcOptions &options) {
}
void DcOptions::constructAddOne(int id, MTPDdcOption::Flags flags, const std::string &ip, int port) {
QWriteLocker lock(&_mutex);
WriteLocker lock(this);
applyOneGuarded(bareDcId(id), flags, ip, port);
}
@@ -167,7 +215,7 @@ QByteArray DcOptions::serialize() const {
return DcOptions().serialize();
}
QReadLocker lock(&_mutex);
ReadLocker lock(this);
auto size = sizeof(qint32);
for (auto &item : _data) {
@@ -175,6 +223,24 @@ QByteArray DcOptions::serialize() const {
size += sizeof(qint32) + item.second.ip.size();
}
auto count = 0;
for (auto &keysInDc : _cdnPublicKeys) {
count += keysInDc.second.size();
}
struct SerializedPublicKey {
DcId dcId;
QByteArray n;
QByteArray e;
};
std::vector<SerializedPublicKey> publicKeys;
publicKeys.reserve(count);
for (auto &keysInDc : _cdnPublicKeys) {
for (auto &entry : keysInDc.second) {
publicKeys.push_back({ keysInDc.first, entry.second.getN(), entry.second.getE() });
size += sizeof(qint32) + Serialize::bytearraySize(publicKeys.back().n) + Serialize::bytearraySize(publicKeys.back().e);
}
}
auto result = QByteArray();
result.reserve(size);
{
@@ -192,6 +258,10 @@ QByteArray DcOptions::serialize() const {
stream << qint32(item.second.ip.size());
stream.writeRawData(item.second.ip.data(), item.second.ip.size());
}
stream << qint32(publicKeys.size());
for (auto &key : publicKeys) {
stream << qint32(key.dcId) << key.n << key.e;
}
}
return result;
}
@@ -205,14 +275,14 @@ void DcOptions::constructFromSerialized(const QByteArray &serialized) {
}
QDataStream stream(&buffer);
stream.setVersion(QDataStream::Qt_5_1);
qint32 count = 0;
auto count = qint32(0);
stream >> count;
if (stream.status() != QDataStream::Ok) {
LOG(("MTP Error: Bad data for DcOptions::constructFromSerialized()"));
return;
}
QWriteLocker lock(&_mutex);
WriteLocker lock(this);
_data.clear();
for (auto i = 0; i != count; ++i) {
qint32 id = 0, flags = 0, port = 0, ipSize = 0;
@@ -227,15 +297,42 @@ void DcOptions::constructFromSerialized(const QByteArray &serialized) {
applyOneGuarded(DcId(id), MTPDdcOption::Flags(flags), ip, port);
}
// Read CDN config
if (!stream.atEnd()) {
auto count = qint32(0);
stream >> count;
if (stream.status() != QDataStream::Ok) {
LOG(("MTP Error: Bad data for CDN config in DcOptions::constructFromSerialized()"));
return;
}
for (auto i = 0; i != count; ++i) {
qint32 dcId = 0;
QByteArray n, e;
stream >> dcId >> n >> e;
if (stream.status() != QDataStream::Ok) {
LOG(("MTP Error: Bad data for CDN config inside DcOptions::constructFromSerialized()"));
return;
}
auto key = internal::RSAPublicKey(n, e);
if (key.isValid()) {
_cdnPublicKeys[dcId].emplace(key.getFingerPrint(), std::move(key));
} else {
LOG(("MTP Error: Could not read valid CDN public key."));
}
}
}
}
DcOptions::Ids DcOptions::sortedDcIds() const {
DcOptions::Ids DcOptions::configEnumDcIds() const {
auto result = Ids();
{
QReadLocker lock(&_mutex);
ReadLocker lock(this);
result.reserve(_data.size());
for (auto &item : _data) {
if (!base::contains(result, item.second.id)) {
if (!isCdnDc(item.second.flags) && !base::contains(result, item.second.id)) {
result.push_back(item.second.id);
}
}
@@ -244,50 +341,171 @@ DcOptions::Ids DcOptions::sortedDcIds() const {
return result;
}
DcId DcOptions::getDefaultDcId() const {
auto result = sortedDcIds();
t_assert(!result.empty());
DcType DcOptions::dcType(ShiftedDcId shiftedDcId) const {
ReadLocker lock(this);
if (_cdnDcIds.find(bareDcId(shiftedDcId)) != _cdnDcIds.cend()) {
return DcType::Cdn;
}
if (isDownloadDcId(shiftedDcId)) {
return DcType::MediaDownload;
}
return DcType::Regular;
}
return result[0];
void DcOptions::setCDNConfig(const MTPDcdnConfig &config) {
WriteLocker lock(this);
_cdnPublicKeys.clear();
for_const (auto &publicKey, config.vpublic_keys.v) {
Expects(publicKey.type() == mtpc_cdnPublicKey);
auto &keyData = publicKey.c_cdnPublicKey();
auto keyBytes = gsl::as_bytes(gsl::make_span(keyData.vpublic_key.v));
auto key = internal::RSAPublicKey(keyBytes);
if (key.isValid()) {
_cdnPublicKeys[keyData.vdc_id.v].emplace(key.getFingerPrint(), std::move(key));
} else {
LOG(("MTP Error: could not read this public RSA key:"));
LOG((qs(keyData.vpublic_key)));
}
}
}
bool DcOptions::hasCDNKeysForDc(DcId dcId) const {
ReadLocker lock(this);
return _cdnPublicKeys.find(dcId) != _cdnPublicKeys.cend();
}
bool DcOptions::getDcRSAKey(DcId dcId, const QVector<MTPlong> &fingerprints, internal::RSAPublicKey *result) const {
auto findKey = [&fingerprints, &result](const std::map<uint64, internal::RSAPublicKey> &keys) {
for_const (auto &fingerprint, fingerprints) {
auto it = keys.find(static_cast<uint64>(fingerprint.v));
if (it != keys.cend()) {
*result = it->second;
return true;
}
}
return false;
};
{
ReadLocker lock(this);
auto it = _cdnPublicKeys.find(dcId);
if (it != _cdnPublicKeys.cend()) {
return findKey(it->second);
}
}
return findKey(_publicKeys);
}
DcOptions::Variants DcOptions::lookup(DcId dcId, DcType type) const {
auto isMediaDownload = (type == DcType::MediaDownload);
int shifts[2][2][4] = {
{ // IPv4
{ // TCP IPv4
isMediaDownload ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only) : -1,
qFlags(MTPDdcOption::Flag::f_tcpo_only),
isMediaDownload ? qFlags(MTPDdcOption::Flag::f_media_only) : -1,
0
}, { // HTTP IPv4
-1,
-1,
isMediaDownload ? qFlags(MTPDdcOption::Flag::f_media_only) : -1,
0
},
}, { // IPv6
{ // TCP IPv6
isMediaDownload ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6) : -1,
MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6,
isMediaDownload ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6) : -1,
qFlags(MTPDdcOption::Flag::f_ipv6)
}, { // HTTP IPv6
-1,
-1,
isMediaDownload ? (MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6) : -1,
qFlags(MTPDdcOption::Flag::f_ipv6)
},
},
auto lookupDesiredFlags = [type](int address, int protocol) -> std::vector<MTPDdcOption::Flags> {
switch (type) {
case DcType::Regular: {
switch (address) {
case Variants::IPv4: {
switch (protocol) {
case Variants::Tcp: return {
// Regular TCP IPv4
qFlags(MTPDdcOption::Flag::f_tcpo_only),
MTPDdcOption::Flags(0)
};
case Variants::Http: return {
// Regular HTTP IPv4
MTPDdcOption::Flags(0),
};
}
} break;
case Variants::IPv6: {
switch (protocol) {
case Variants::Tcp: return {
// Regular TCP IPv6
(MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6),
qFlags(MTPDdcOption::Flag::f_ipv6),
};
case Variants::Http: return {
// Regular HTTP IPv6
qFlags(MTPDdcOption::Flag::f_ipv6),
};
}
} break;
}
} break;
case DcType::MediaDownload: {
switch (address) {
case Variants::IPv4: {
switch (protocol) {
case Variants::Tcp: return {
// Media download TCP IPv4
(MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only),
qFlags(MTPDdcOption::Flag::f_tcpo_only),
qFlags(MTPDdcOption::Flag::f_media_only),
MTPDdcOption::Flags(0),
};
case Variants::Http: return {
// Media download HTTP IPv4
qFlags(MTPDdcOption::Flag::f_media_only),
MTPDdcOption::Flags(0),
};
}
} break;
case Variants::IPv6: {
switch (protocol) {
case Variants::Tcp: return {
// Media download TCP IPv6
(MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6),
(MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6),
(MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6),
qFlags(MTPDdcOption::Flag::f_ipv6)
};
case Variants::Http: return {
// Media download HTTP IPv6
(MTPDdcOption::Flag::f_media_only | MTPDdcOption::Flag::f_ipv6),
qFlags(MTPDdcOption::Flag::f_ipv6),
};
}
} break;
}
} break;
case DcType::Cdn: {
switch (address) {
case Variants::IPv4: {
switch (protocol) {
case Variants::Tcp: return {
// CDN TCP IPv4
(MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_tcpo_only),
qFlags(MTPDdcOption::Flag::f_cdn),
};
case Variants::Http: return {
// CDN HTTP IPv4
qFlags(MTPDdcOption::Flag::f_cdn),
};
}
} break;
case Variants::IPv6: {
switch (protocol) {
case Variants::Tcp: return {
// CDN TCP IPv6
(MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_tcpo_only | MTPDdcOption::Flag::f_ipv6),
(MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6),
};
case Variants::Http: return {
// CDN HTTP IPv6
(MTPDdcOption::Flag::f_cdn | MTPDdcOption::Flag::f_ipv6),
};
}
} break;
}
} break;
}
Unexpected("Bad type / address / protocol");
};
auto result = Variants();
{
QReadLocker lock(&_mutex);
ReadLocker lock(this);
for (auto address = 0; address != Variants::AddressTypeCount; ++address) {
for (auto protocol = 0; protocol != Variants::ProtocolCount; ++protocol) {
for (auto variant = 0; variant != base::array_size(shifts[address][protocol]); ++variant) {
auto shift = shifts[address][protocol][variant];
auto desiredFlags = lookupDesiredFlags(address, protocol);
for (auto flags : desiredFlags) {
auto shift = static_cast<int>(flags);
if (shift < 0) continue;
auto it = _data.find(shiftDcId(dcId, shift));
@@ -304,6 +522,15 @@ DcOptions::Variants DcOptions::lookup(DcId dcId, DcType type) const {
return result;
}
void DcOptions::computeCdnDcIds() {
_cdnDcIds.clear();
for (auto &item : _data) {
if (item.second.flags & MTPDdcOption::Flag::f_cdn) {
_cdnDcIds.insert(item.second.id);
}
}
}
bool DcOptions::loadFromFile(const QString &path) {
QVector<MTPDcOption> options;
@@ -372,7 +599,7 @@ bool DcOptions::writeToFile(const QString &path) const {
QTextStream stream(&f);
stream.setCodec("UTF-8");
QReadLocker lock(&_mutex);
ReadLocker lock(this);
for (auto &item : _data) {
auto &endpoint = item.second;
stream << endpoint.id << ' ' << QString::fromStdString(endpoint.ip) << ' ' << endpoint.port;

View File

@@ -21,12 +21,18 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "base/observer.h"
#include "mtproto/rsa_public_key.h"
#include <string>
#include <vector>
#include <map>
namespace MTP {
enum class DcType {
Regular,
MediaDownload,
Cdn,
};
class DcOptions {
public:
// construct methods don't notify "changed" subscribers.
@@ -41,10 +47,9 @@ public:
}
void setFromList(const MTPVector<MTPDcOption> &options);
void addFromList(const MTPVector<MTPDcOption> &options);
void addFromOther(const DcOptions &options);
void addFromOther(DcOptions &&options);
Ids sortedDcIds() const;
DcId getDefaultDcId() const;
Ids configEnumDcIds() const;
struct Endpoint {
std::string ip;
@@ -64,11 +69,12 @@ public:
};
Endpoint data[AddressTypeCount][ProtocolCount];
};
enum class DcType {
Regular,
MediaDownload,
};
Variants lookup(DcId dcId, DcType type) const;
DcType dcType(ShiftedDcId shiftedDcId) const;
void setCDNConfig(const MTPDcdnConfig &config);
bool hasCDNKeysForDc(DcId dcId) const;
bool getDcRSAKey(DcId dcId, const QVector<MTPlong> &fingerprints, internal::RSAPublicKey *result) const;
// Debug feature for now.
bool loadFromFile(const QString &path);
@@ -87,9 +93,21 @@ private:
bool applyOneGuarded(DcId dcId, MTPDdcOption::Flags flags, const std::string &ip, int port);
void processFromList(const QVector<MTPDcOption> &options, bool overwrite);
void computeCdnDcIds();
std::map<int, Option> _data;
mutable QReadWriteLock _mutex;
void readBuiltInPublicKeys();
class WriteLocker;
friend class WriteLocker;
class ReadLocker;
friend class ReadLocker;
std::map<ShiftedDcId, Option> _data;
std::set<DcId> _cdnDcIds;
std::map<uint64, internal::RSAPublicKey> _publicKeys;
std::map<DcId, std::map<uint64, internal::RSAPublicKey>> _cdnPublicKeys;
mutable QReadWriteLock _useThroughLockers;
mutable base::Observable<Ids> _changed;

View File

@@ -78,7 +78,7 @@ void ConfigLoader::load() {
sendRequest(_instance->mainDcId());
_enumDCTimer.start(kEnumerateDcTimeout);
} else {
auto ids = _instance->dcOptions()->sortedDcIds();
auto ids = _instance->dcOptions()->configEnumDcIds();
t_assert(!ids.empty());
_enumCurrent = ids.front();
enumDC();
@@ -108,7 +108,7 @@ void ConfigLoader::enumDC() {
} else {
_instance->killSession(MTP::configDcId(_enumCurrent));
}
auto ids = _instance->dcOptions()->sortedDcIds();
auto ids = _instance->dcOptions()->configEnumDcIds();
t_assert(!ids.empty());
auto i = std::find(ids.cbegin(), ids.cend(), _enumCurrent);

View File

@@ -86,10 +86,13 @@ constexpr ShiftedDcId logoutDcId(DcId dcId) {
return shiftDcId(dcId, internal::kLogoutDcShift);
}
constexpr auto kDownloadSessionsCount = 2;
constexpr auto kUploadSessionsCount = 2;
namespace internal {
constexpr ShiftedDcId downloadDcId(DcId dcId, int index) {
static_assert(MTPDownloadSessionsCount < internal::kMaxMediaDcCount, "Too large MTPDownloadSessionsCount!");
static_assert(kDownloadSessionsCount < internal::kMaxMediaDcCount, "Too large MTPDownloadSessionsCount!");
return shiftDcId(dcId, internal::kBaseDownloadDcShift + index);
};
@@ -97,18 +100,22 @@ constexpr ShiftedDcId downloadDcId(DcId dcId, int index) {
// send(req, callbacks, MTP::downloadDcId(dc, index)) - for download shifted dc id
inline ShiftedDcId downloadDcId(DcId dcId, int index) {
t_assert(index >= 0 && index < MTPDownloadSessionsCount);
Expects(index >= 0 && index < kDownloadSessionsCount);
return internal::downloadDcId(dcId, index);
}
constexpr bool isDownloadDcId(ShiftedDcId shiftedDcId) {
return (shiftedDcId >= internal::downloadDcId(0, 0)) && (shiftedDcId < internal::downloadDcId(0, MTPDownloadSessionsCount - 1) + internal::kDcShift);
inline constexpr bool isDownloadDcId(ShiftedDcId shiftedDcId) {
return (shiftedDcId >= internal::downloadDcId(0, 0)) && (shiftedDcId < internal::downloadDcId(0, kDownloadSessionsCount - 1) + internal::kDcShift);
}
inline bool isCdnDc(MTPDdcOption::Flags flags) {
return (flags & MTPDdcOption::Flag::f_cdn);
}
namespace internal {
constexpr ShiftedDcId uploadDcId(DcId dcId, int index) {
static_assert(MTPUploadSessionsCount < internal::kMaxMediaDcCount, "Too large MTPUploadSessionsCount!");
static_assert(kUploadSessionsCount < internal::kMaxMediaDcCount, "Too large MTPUploadSessionsCount!");
return shiftDcId(dcId, internal::kBaseUploadDcShift + index);
};
@@ -117,12 +124,12 @@ constexpr ShiftedDcId uploadDcId(DcId dcId, int index) {
// send(req, callbacks, MTP::uploadDcId(index)) - for upload shifted dc id
// uploading always to the main dc so bareDcId == 0
inline ShiftedDcId uploadDcId(int index) {
t_assert(index >= 0 && index < MTPUploadSessionsCount);
Expects(index >= 0 && index < kUploadSessionsCount);
return internal::uploadDcId(0, index);
};
constexpr bool isUploadDcId(ShiftedDcId shiftedDcId) {
return (shiftedDcId >= internal::uploadDcId(0, 0)) && (shiftedDcId < internal::uploadDcId(0, MTPUploadSessionsCount - 1) + internal::kDcShift);
return (shiftedDcId >= internal::uploadDcId(0, 0)) && (shiftedDcId < internal::uploadDcId(0, kUploadSessionsCount - 1) + internal::kDcShift);
}
inline ShiftedDcId destroyKeyNextDcId(ShiftedDcId shiftedDcId) {

View File

@@ -25,10 +25,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "auth_session.h"
#include "messenger.h"
#include "mtproto/connection.h"
#include "mtproto/sender.h"
#include "mtproto/rsa_public_key.h"
namespace MTP {
class Instance::Private {
class Instance::Private : public Sender {
public:
Private(Instance *instance, DcOptions *options, Instance::Mode mode);
@@ -45,6 +47,7 @@ public:
DcOptions *dcOptions();
void configLoadRequest();
void cdnConfigLoadRequest();
void restart();
void restart(ShiftedDcId shiftedDcId);
@@ -116,6 +119,9 @@ private:
void configLoadDone(const MTPConfig &result);
bool configLoadFail(const RPCError &error);
void cdnConfigLoadDone(const MTPCdnConfig &result);
bool cdnConfigLoadFail(const RPCError &error);
void checkDelayedRequests();
Instance *_instance = nullptr;
@@ -133,6 +139,7 @@ private:
base::set_of_unique_ptr<internal::Connection> _quittingConnections;
std::unique_ptr<internal::ConfigLoader> _configLoader;
mtpRequestId _cdnConfigLoadRequestId = 0;
std::map<DcId, AuthKeyPtr> _keysForWrite;
mutable QReadWriteLock _keysForWriteLock;
@@ -174,7 +181,7 @@ private:
};
Instance::Private::Private(Instance *instance, DcOptions *options, Instance::Mode mode) : _instance(instance)
Instance::Private::Private(Instance *instance, DcOptions *options, Instance::Mode mode) : Sender(instance), _instance(instance)
, _dcOptions(options)
, _mode(mode) {
}
@@ -188,7 +195,6 @@ void Instance::Private::start(Config &&config) {
for (auto &key : config.keys) {
auto dcId = key->dcId();
auto shiftedDcId = dcId;
if (isKeysDestroyer()) {
shiftedDcId = MTP::destroyKeyNextDcId(shiftedDcId);
@@ -273,6 +279,22 @@ void Instance::Private::configLoadRequest() {
_configLoader->load();
}
void Instance::Private::cdnConfigLoadRequest() {
if (_cdnConfigLoadRequestId || _mainDcId == Config::kNoneMainDc) {
return;
}
_cdnConfigLoadRequestId = request(MTPhelp_GetCdnConfig()).done([this](const MTPCdnConfig &result) {
_cdnConfigLoadRequestId = 0;
Expects(result.type() == mtpc_cdnConfig);
dcOptions()->setCDNConfig(result.c_cdnConfig());
Local::writeSettings();
emit _instance->cdnConfigLoaded();
}).send();
}
void Instance::Private::restart() {
for (auto &session : _sessions) {
session.second->restart();
@@ -423,7 +445,7 @@ void Instance::Private::logout(RPCDoneHandlerPtr onDone, RPCFailHandlerPtr onFai
}
}
for (auto dcId : dcIds) {
if (dcId != mainDcId()) {
if (dcId != mainDcId() && dcOptions()->dcType(dcId) != DcType::Cdn) {
auto shiftedDcId = MTP::logoutDcId(dcId);
auto requestId = _instance->send(MTPauth_LogOut(), rpcDone([this](mtpRequestId requestId) {
logoutGuestDone(requestId);
@@ -1204,6 +1226,8 @@ void Instance::Private::prepareToDestroy() {
// It accesses Instance in destructor, so it should be destroyed first.
_configLoader.reset();
requestCancellingDiscard();
for (auto &session : base::take(_sessions)) {
session.second->kill();
}
@@ -1233,6 +1257,10 @@ void Instance::configLoadRequest() {
_private->configLoadRequest();
}
void Instance::cdnConfigLoadRequest() {
_private->cdnConfigLoadRequest();
}
void Instance::connectionFinished(internal::Connection *connection) {
_private->connectionFinished(connection);
}

View File

@@ -119,14 +119,18 @@ public:
bool isKeysDestroyer() const;
void scheduleKeyDestroy(ShiftedDcId shiftedDcId);
void configLoadRequest();
void cdnConfigLoadRequest();
~Instance();
public slots:
void configLoadRequest();
void connectionFinished(internal::Connection *connection);
signals:
void configLoaded();
void cdnConfigLoaded();
void keyDestroyed(qint32 shiftedDcId);
void allKeysDestroyed();

View File

@@ -25,55 +25,110 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include <openssl/bio.h>
#include <openssl/err.h>
using std::string;
namespace MTP {
namespace internal {
struct RSAPublicKey::Impl {
Impl(const char *key) : rsa(PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<char*>(key), -1), 0, 0, 0)) {
class RSAPublicKey::Private {
public:
Private(base::const_byte_span key) : _rsa(PEM_read_bio_RSAPublicKey(BIO_new_mem_buf(const_cast<gsl::byte*>(key.data()), key.size()), 0, 0, 0)) {
if (_rsa) {
computeFingerprint();
}
}
~Impl() {
RSA_free(rsa);
Private(const QByteArray &n, const QByteArray &e) : _rsa(RSA_new()) {
if (_rsa) {
_rsa->n = BN_bin2bn((const uchar*)n.data(), n.size(), _rsa->n);
_rsa->e = BN_bin2bn((const uchar*)e.data(), e.size(), _rsa->e);
if (!_rsa->n || !_rsa->e) {
RSA_free(base::take(_rsa));
} else {
computeFingerprint();
}
}
}
RSA *rsa;
uint64 fp = 0;
QByteArray getN() const {
Expects(isValid());
return toBytes(_rsa->n);
}
QByteArray getE() const {
Expects(isValid());
return toBytes(_rsa->e);
}
uint64 getFingerPrint() const {
return _fingerprint;
}
bool isValid() const {
return _rsa != nullptr;
}
bool encrypt(const void *data, string &result) const {
Expects(isValid());
result.resize(256);
auto res = RSA_public_encrypt(256, reinterpret_cast<const unsigned char*>(data), reinterpret_cast<uchar*>(&result[0]), _rsa, RSA_NO_PADDING);
if (res != 256) {
ERR_load_crypto_strings();
LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(getFingerPrint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0)));
return false;
}
return true;
}
~Private() {
RSA_free(_rsa);
}
private:
void computeFingerprint() {
Expects(isValid());
mtpBuffer string;
MTP_bytes(toBytes(_rsa->n)).write(string);
MTP_bytes(toBytes(_rsa->e)).write(string);
uchar sha1Buffer[20];
_fingerprint = *(uint64*)(hashSha1(&string[0], string.size() * sizeof(mtpPrime), sha1Buffer) + 3);
}
static QByteArray toBytes(BIGNUM *number) {
auto size = static_cast<int>(BN_num_bytes(number));
auto result = QByteArray(size, 0);
BN_bn2bin(number, reinterpret_cast<uchar*>(result.data()));
return result;
}
RSA *_rsa = nullptr;
uint64 _fingerprint = 0;
};
RSAPublicKey::RSAPublicKey(const char *key) : impl_(new Impl(key)) {
if (!impl_->rsa) return;
int nBytes = BN_num_bytes(impl_->rsa->n);
int eBytes = BN_num_bytes(impl_->rsa->e);
std::string nStr(nBytes, 0), eStr(eBytes, 0);
BN_bn2bin(impl_->rsa->n, (uchar*)&nStr[0]);
BN_bn2bin(impl_->rsa->e, (uchar*)&eStr[0]);
mtpBuffer tmp;
MTP_string(nStr).write(tmp);
MTP_string(eStr).write(tmp);
uchar sha1Buffer[20];
impl_->fp = *(uint64*)(hashSha1(&tmp[0], tmp.size() * sizeof(mtpPrime), sha1Buffer) + 3);
RSAPublicKey::RSAPublicKey(base::const_byte_span key) : _private(std::make_shared<Private>(key)) {
}
uint64 RSAPublicKey::getFingerPrint() const {
return impl_->fp;
RSAPublicKey::RSAPublicKey(const QByteArray &n, const QByteArray &e) : _private(std::make_shared<Private>(n, e)) {
}
bool RSAPublicKey::isValid() const {
return impl_->rsa != nullptr;
return _private && _private->isValid();
}
bool RSAPublicKey::encrypt(const void *data, std::string &result) const {
uint64 RSAPublicKey::getFingerPrint() const {
Expects(isValid());
return _private->getFingerPrint();
}
result.resize(256);
int res = RSA_public_encrypt(256, reinterpret_cast<const unsigned char*>(data), reinterpret_cast<uchar*>(&result[0]), impl_->rsa, RSA_NO_PADDING);
if (res != 256) {
ERR_load_crypto_strings();
LOG(("RSA Error: RSA_public_encrypt failed, key fp: %1, result: %2, error: %3").arg(getFingerPrint()).arg(res).arg(ERR_error_string(ERR_get_error(), 0)));
return false;
}
return true;
QByteArray RSAPublicKey::getN() const {
Expects(isValid());
return _private->getN();
}
QByteArray RSAPublicKey::getE() const {
Expects(isValid());
return _private->getE();
}
bool RSAPublicKey::encrypt(const void *data, string &result) const {
Expects(isValid());
return _private->encrypt(data, result);
}
} // namespace internal

View File

@@ -26,21 +26,26 @@ namespace internal {
// this class holds an RSA public key and can encrypt fixed-size messages with it
class RSAPublicKey final {
public:
// key in RSAPublicKey "-----BEGIN RSA PUBLIC KEY----- ..." format
RSAPublicKey(const char *key);
RSAPublicKey() = default;
RSAPublicKey(base::const_byte_span key);
RSAPublicKey(const QByteArray &n, const QByteArray &e);
RSAPublicKey(RSAPublicKey &&other) = default;
RSAPublicKey(const RSAPublicKey &other) = default;
RSAPublicKey &operator=(RSAPublicKey &&other) = default;
RSAPublicKey &operator=(const RSAPublicKey &other) = default;
bool isValid() const;
uint64 getFingerPrint() const;
QByteArray getN() const;
QByteArray getE() const;
// data has exactly 256 chars to be encrypted
bool encrypt(const void *data, std::string &result) const;
private:
struct Impl;
typedef QSharedPointer<Impl> ImplPtr;
ImplPtr impl_;
class Private;
std::shared_ptr<Private> _private;
};

View File

@@ -290,6 +290,11 @@ public:
void requestSendDelayed() {
MTP::sendAnything();
}
void requestCancellingDiscard() {
for (auto &request : _requests) {
request.handled();
}
}
private:
class RequestWrap {

View File

@@ -59,8 +59,7 @@ void PasscodeWidget::onSubmit() {
if (App::main()) {
if (Local::checkPasscode(_passcode->text().toUtf8())) {
cSetPasscodeBadTries(0);
App::wnd()->clearPasscode(); // Destroys this widget.
Messenger::Instance().clearPasscode(); // Destroys this widget.
return;
} else {
cSetPasscodeBadTries(cPasscodeBadTries() + 1);

View File

@@ -51,13 +51,17 @@ QString strNeedToRefresh2() {
NSString *fullname;
NSURL *app;
NSImage *icon;
}
@property (nonatomic, retain) NSString *fullname;
@property (nonatomic, retain) NSURL *app;
@property (nonatomic, retain) NSImage *icon;
@end
@end // @interface OpenWithApp
@implementation OpenWithApp
@synthesize fullname, app, icon;
- (void) dealloc {
@@ -67,7 +71,7 @@ QString strNeedToRefresh2() {
[super dealloc];
}
@end
@end // @implementation OpenWithApp
@interface OpenFileWithInterface : NSObject {
}
@@ -77,7 +81,7 @@ QString strNeedToRefresh2() {
- (void) itemChosen:(id)sender;
- (void) dealloc;
@end
@end // @interface OpenFileWithInterface
@implementation OpenFileWithInterface {
NSString *toOpen;
@@ -89,6 +93,7 @@ QString strNeedToRefresh2() {
NSMutableArray *apps;
NSMenu *menu;
}
- (void) fillAppByUrl:(NSURL*)url bundle:(NSString**)bundle name:(NSString**)name version:(NSString**)version icon:(NSImage**)icon {
@@ -233,13 +238,13 @@ QString strNeedToRefresh2() {
[super dealloc];
}
@end
@end // @implementation OpenFileWithInterface
@interface NSURL(CompareUrls)
- (BOOL) isEquivalent:(NSURL *)aURL;
@end
@end // @interface NSURL(CompareUrls)
@implementation NSURL(CompareUrls)
@@ -253,7 +258,7 @@ QString strNeedToRefresh2() {
return YES;
}
@end
@end // @implementation NSURL(CompareUrls)
@interface ChooseApplicationDelegate : NSObject<NSOpenSavePanelDelegate> {
}
@@ -264,7 +269,7 @@ QString strNeedToRefresh2() {
- (void) menuDidClose;
- (void) dealloc;
@end
@end // @interface ChooseApplicationDelegate
@implementation ChooseApplicationDelegate {
BOOL onlyRecommended;
@@ -275,6 +280,7 @@ QString strNeedToRefresh2() {
NSImageView *icon;
NSString *recom;
NSView *accessory;
}
- (id) init:(NSArray *)recommendedApps withPanel:(NSOpenPanel *)creator withSelector:(NSPopUpButton *)menu withGood:(NSTextField *)goodLabel withBad:(NSTextField *)badLabel withIcon:(NSImageView *)badIcon withAccessory:(NSView *)acc {
@@ -379,7 +385,7 @@ QString strNeedToRefresh2() {
[super dealloc];
}
@end
@end // @implementation ChooseApplicationDelegate
namespace Platform {
namespace File {

View File

@@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "platform/platform_main_window.h"
#include "platform/mac/specific_mac_p.h"
#include "base/timer.h"
namespace Platform {
@@ -65,9 +66,6 @@ public slots:
void psMacDelete();
void psMacSelectAll();
private slots:
void onHideAfterFullScreen();
protected:
bool eventFilter(QObject *obj, QEvent *evt) override;
@@ -119,7 +117,7 @@ private:
mutable bool psIdle;
mutable QTimer psIdleTimer;
QTimer _hideAfterFullScreenTimer;
base::Timer _hideAfterFullScreenTimer;
QMenuBar psMainMenu;
QAction *psLogout = nullptr;

View File

@@ -37,6 +37,15 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include <IOKit/hidsystem/ev_keymap.h>
#include <SPMediaKeyTap.h>
namespace {
// When we close a window that is fullscreen we first leave the fullscreen
// mode and after that hide the window. This is a timeout for elaving the
// fullscreen mode, after that we'll hide the window no matter what.
constexpr auto kHideAfterFullscreenTimeoutMs = 3000;
} // namespace
@interface MainWindowObserver : NSObject {
}
@@ -48,7 +57,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
- (void) windowWillEnterFullScreen:(NSNotification *)aNotification;
- (void) windowWillExitFullScreen:(NSNotification *)aNotification;
@end
@end // @interface MainWindowObserver
namespace Platform {
@@ -105,8 +114,7 @@ public:
} // namespace Platform
@implementation MainWindowObserver {
MainWindow::Private *_private;
MainWindow::Private *_private;
}
@@ -141,7 +149,7 @@ MainWindow::Private *_private;
_private->willExitFullScreen();
}
@end
@end // @implementation MainWindowObserver
namespace Platform {
@@ -239,8 +247,7 @@ MainWindow::MainWindow()
trayImg = st::macTrayIcon.instance(QColor(0, 0, 0, 180), dbisOne);
trayImgSel = st::macTrayIcon.instance(QColor(255, 255, 255), dbisOne);
_hideAfterFullScreenTimer.setSingleShot(true);
connect(&_hideAfterFullScreenTimer, SIGNAL(timeout()), this, SLOT(onHideAfterFullScreen()));
_hideAfterFullScreenTimer.setCallback([this] { hideAndDeactivate(); });
}
void MainWindow::closeWithoutDestroy() {
@@ -248,7 +255,7 @@ void MainWindow::closeWithoutDestroy() {
auto isFullScreen = (([nsWindow styleMask] & NSFullScreenWindowMask) == NSFullScreenWindowMask);
if (isFullScreen) {
_hideAfterFullScreenTimer.start(3000);
_hideAfterFullScreenTimer.callOnce(kHideAfterFullscreenTimeoutMs);
[nsWindow toggleFullScreen:nsWindow];
} else {
hideAndDeactivate();
@@ -257,8 +264,7 @@ void MainWindow::closeWithoutDestroy() {
void MainWindow::stateChangedHook(Qt::WindowState state) {
if (_hideAfterFullScreenTimer.isActive()) {
_hideAfterFullScreenTimer.stop();
QTimer::singleShot(0, this, SLOT(onHideAfterFullScreen()));
_hideAfterFullScreenTimer.callOnce(0);
}
}
@@ -278,14 +284,8 @@ void MainWindow::titleVisibilityChangedHook() {
updateTitleCounter();
}
void MainWindow::onHideAfterFullScreen() {
hideAndDeactivate();
}
void MainWindow::hideAndDeactivate() {
hide();
NSWindow *nsWindow = [reinterpret_cast<NSView*>(winId()) window];
[[NSApplication sharedApplication] hide: nsWindow];
}
QImage MainWindow::psTrayIcon(bool selected) const {

View File

@@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#pragma once
#include "platform/platform_notifications_manager.h"
#include "base/weak_unique_ptr.h"
namespace Platform {
namespace Notifications {
@@ -28,7 +29,7 @@ namespace Notifications {
bool SkipAudio();
bool SkipToast();
class Manager : public Window::Notifications::NativeManager {
class Manager : public Window::Notifications::NativeManager, public base::enable_weak_from_this {
public:
Manager(Window::Notifications::System *system);
~Manager();

View File

@@ -25,7 +25,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "styles/style_window.h"
#include "mainwindow.h"
#include "base/task_queue.h"
#include "base/variant.h"
#include <thread>
#include <Cocoa/Cocoa.h>
namespace {
@@ -54,37 +56,51 @@ NSImage *qt_mac_create_nsimage(const QPixmap &pm);
@interface NotificationDelegate : NSObject<NSUserNotificationCenterDelegate> {
}
- (id) initWithManager:(std::shared_ptr<Manager*>)manager;
- (void)userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification;
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification;
- (id) initWithManager:(base::weak_unique_ptr<Manager>)manager managerId:(uint64)managerId;
- (void) userNotificationCenter:(NSUserNotificationCenter*)center didActivateNotification:(NSUserNotification*)notification;
- (BOOL) userNotificationCenter:(NSUserNotificationCenter*)center shouldPresentNotification:(NSUserNotification*)notification;
@end
@end // @interface NotificationDelegate
@implementation NotificationDelegate {
std::weak_ptr<Manager*> _manager;
base::weak_unique_ptr<Manager> _manager;
uint64 _managerId;
}
- (id) initWithManager:(std::shared_ptr<Manager*>)manager {
- (id) initWithManager:(base::weak_unique_ptr<Manager>)manager managerId:(uint64)managerId {
if (self = [super init]) {
_manager = manager;
_managerId = managerId;
}
return self;
}
- (void) userNotificationCenter:(NSUserNotificationCenter *)center didActivateNotification:(NSUserNotification *)notification {
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *launchIdObject = [notificationUserInfo objectForKey:@"launch"];
auto notificationLaunchId = launchIdObject ? [launchIdObject unsignedLongLongValue] : 0ULL;
DEBUG_LOG(("Received notification with instance %1").arg(notificationLaunchId));
if (notificationLaunchId != Global::LaunchId()) { // other app instance notification
NSNumber *managerIdObject = [notificationUserInfo objectForKey:@"manager"];
auto notificationManagerId = managerIdObject ? [managerIdObject unsignedLongLongValue] : 0ULL;
DEBUG_LOG(("Received notification with instance %1, mine: %2").arg(notificationManagerId).arg(_managerId));
if (notificationManagerId != _managerId) { // other app instance notification
base::TaskQueue::Main().Put([] {
// Usually we show and activate main window when the application
// is activated (receives applicationDidBecomeActive: notification).
//
// This is used for window show in Cmd+Tab switching to the application.
//
// But when a notification arrives sometimes macOS still activates the app
// and we receive applicationDidBecomeActive: notification even if the
// notification was sent by another instance of the application. In that case
// we set a flag for a couple of seconds to ignore this app activation.
objc_ignoreApplicationActivationRightNow();
});
return;
}
NSNumber *peerObject = [notificationUserInfo objectForKey:@"peer"];
auto notificationPeerId = peerObject ? [peerObject unsignedLongLongValue] : 0ULL;
if (!notificationPeerId) {
LOG(("App Error: A notification with unknown peer was received"));
return;
}
@@ -92,23 +108,27 @@ std::weak_ptr<Manager*> _manager;
auto notificationMsgId = msgObject ? [msgObject intValue] : 0;
if (notification.activationType == NSUserNotificationActivationTypeReplied) {
auto notificationReply = QString::fromUtf8([[[notification response] string] UTF8String]);
if (auto manager = _manager.lock()) {
(*manager)->notificationReplied(notificationPeerId, notificationMsgId, notificationReply);
}
base::TaskQueue::Main().Put([manager = _manager, notificationPeerId, notificationMsgId, notificationReply] {
if (manager) {
manager->notificationReplied(notificationPeerId, notificationMsgId, notificationReply);
}
});
} else if (notification.activationType == NSUserNotificationActivationTypeContentsClicked) {
if (auto manager = _manager.lock()) {
(*manager)->notificationActivated(notificationPeerId, notificationMsgId);
}
base::TaskQueue::Main().Put([manager = _manager, notificationPeerId, notificationMsgId] {
if (manager) {
manager->notificationActivated(notificationPeerId, notificationMsgId);
}
});
}
[center removeDeliveredNotification: notification];
}
- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification {
- (BOOL) userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification {
return YES;
}
@end
@end // @implementation NotificationDelegate
namespace Platform {
namespace Notifications {
@@ -156,6 +176,7 @@ void CustomNotificationShownHook(QWidget *widget) {
class Manager::Private : public QObject, private base::Subscriber {
public:
Private(Manager *manager);
void showNotification(PeerData *peer, MsgId msgId, const QString &title, const QString &subtitle, const QString &msg, bool hideNameAndPhoto, bool hideReplyButton);
void clearAll();
void clearFromHistory(History *history);
@@ -164,14 +185,36 @@ public:
~Private();
private:
std::shared_ptr<Manager*> _guarded;
template <typename Task>
void putClearTask(Task task);
void clearingThreadLoop();
const uint64 _managerId = 0;
QString _managerIdString;
NotificationDelegate *_delegate = nullptr;
std::thread _clearingThread;
std::mutex _clearingMutex;
std::condition_variable _clearingCondition;
struct ClearFromHistory {
PeerId peerId;
};
struct ClearAll {
};
struct ClearFinish {
};
using ClearTask = base::variant<ClearFromHistory, ClearAll, ClearFinish>;
std::vector<ClearTask> _clearingTasks;
};
Manager::Private::Private(Manager *manager)
: _guarded(std::make_shared<Manager*>(manager))
, _delegate([[NotificationDelegate alloc] initWithManager:_guarded]) {
: _managerId(rand_value<uint64>())
, _managerIdString(QString::number(_managerId))
, _delegate([[NotificationDelegate alloc] initWithManager:manager managerId:_managerId]) {
updateDelegate();
subscribe(Global::RefWorkMode(), [this](DBIWorkMode mode) {
// We need to update the delegate _after_ the tray icon change was done in Qt.
@@ -187,11 +230,11 @@ void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QStri
NSUserNotification *notification = [[[NSUserNotification alloc] init] autorelease];
if ([notification respondsToSelector:@selector(setIdentifier:)]) {
auto identifier = QString::number(Global::LaunchId()) + '_' + QString::number(peer->id) + '_' + QString::number(msgId);
auto identifier = _managerIdString + '_' + QString::number(peer->id) + '_' + QString::number(msgId);
auto identifierValue = Q2NSString(identifier);
[notification setIdentifier:identifierValue];
}
[notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong:peer->id],@"peer",[NSNumber numberWithInt:msgId],@"msgid",[NSNumber numberWithUnsignedLongLong:Global::LaunchId()],@"launch",nil]];
[notification setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithUnsignedLongLong:peer->id],@"peer",[NSNumber numberWithInt:msgId],@"msgid",[NSNumber numberWithUnsignedLongLong:_managerId],@"manager",nil]];
[notification setTitle:Q2NSString(title)];
[notification setSubtitle:Q2NSString(subtitle)];
@@ -214,35 +257,72 @@ void Manager::Private::showNotification(PeerData *peer, MsgId msgId, const QStri
}
}
void Manager::Private::clearAll() {
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
NSArray *notificationsList = [center deliveredNotifications];
for (id notification in notificationsList) {
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *launchIdObject = [notificationUserInfo objectForKey:@"launch"];
auto notificationLaunchId = launchIdObject ? [launchIdObject unsignedLongLongValue] : 0ULL;
if (notificationLaunchId == Global::LaunchId()) {
[center removeDeliveredNotification:notification];
void Manager::Private::clearingThreadLoop() {
auto finished = false;
while (!finished) {
auto clearAll = false;
auto clearFromPeers = std::set<PeerId>(); // Better to use flatmap.
{
std::unique_lock<std::mutex> lock(_clearingMutex);
while (_clearingTasks.empty()) {
_clearingCondition.wait(lock);
}
for (auto &task : _clearingTasks) {
if (base::get_if<ClearFinish>(&task)) {
finished = true;
clearAll = true;
} else if (base::get_if<ClearAll>(&task)) {
clearAll = true;
} else if (auto fromHistory = base::get_if<ClearFromHistory>(&task)) {
clearFromPeers.insert(fromHistory->peerId);
}
}
_clearingTasks.clear();
}
auto clearByPeer = [&clearFromPeers](NSDictionary *notificationUserInfo) {
if (NSNumber *peerObject = [notificationUserInfo objectForKey:@"peer"]) {
auto notificationPeerId = [peerObject unsignedLongLongValue];
if (notificationPeerId) {
return (clearFromPeers.find(notificationPeerId) != clearFromPeers.cend());
}
}
return true;
};
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
NSArray *notificationsList = [center deliveredNotifications];
for (id notification in notificationsList) {
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *managerIdObject = [notificationUserInfo objectForKey:@"manager"];
auto notificationManagerId = managerIdObject ? [managerIdObject unsignedLongLongValue] : 0ULL;
if (notificationManagerId == _managerId) {
if (clearAll || clearByPeer(notificationUserInfo)) {
[center removeDeliveredNotification:notification];
}
}
}
}
[center removeAllDeliveredNotifications];
}
template <typename Task>
void Manager::Private::putClearTask(Task task) {
if (!_clearingThread.joinable()) {
_clearingThread = std::thread([this] { clearingThreadLoop(); });
}
std::unique_lock<std::mutex> lock(_clearingMutex);
_clearingTasks.push_back(task);
_clearingCondition.notify_one();
}
void Manager::Private::clearAll() {
putClearTask(ClearAll());
}
void Manager::Private::clearFromHistory(History *history) {
unsigned long long peerId = history->peer->id;
NSUserNotificationCenter *center = [NSUserNotificationCenter defaultUserNotificationCenter];
NSArray *notificationsList = [center deliveredNotifications];
for (id notification in notificationsList) {
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *launchIdObject = [notificationUserInfo objectForKey:@"launch"];
NSNumber *peerObject = [notificationUserInfo objectForKey:@"peer"];
auto notificationLaunchId = launchIdObject ? [launchIdObject unsignedLongLongValue] : 0ULL;
auto notificationPeerId = peerObject ? [peerObject unsignedLongLongValue] : 0ULL;
if (notificationPeerId == peerId && notificationLaunchId == Global::LaunchId()) {
[center removeDeliveredNotification:notification];
}
}
putClearTask(ClearFromHistory { history->peer->id });
}
void Manager::Private::updateDelegate() {
@@ -251,7 +331,10 @@ void Manager::Private::updateDelegate() {
}
Manager::Private::~Private() {
clearAll();
if (_clearingThread.joinable()) {
putClearTask(ClearFinish());
_clearingThread.join();
}
[_delegate release];
}

View File

@@ -31,6 +31,7 @@ bool objc_idleSupported();
bool objc_idleTime(TimeMs &idleTime);
void objc_start();
void objc_ignoreApplicationActivationRightNow();
void objc_finish();
bool objc_execUpdater();
void objc_execTelegram(const QString &crashreport);

View File

@@ -26,6 +26,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "platform/mac/mac_utilities.h"
#include "styles/style_window.h"
#include "lang.h"
#include "base/timer.h"
#include <Cocoa/Cocoa.h>
#include <CoreFoundation/CFURL.h>
@@ -33,6 +34,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include <IOKit/hidsystem/ev_keymap.h>
#include <SPMediaKeyTap.h>
namespace {
constexpr auto kIgnoreActivationTimeoutMs = 1500;
} // namespace
using Platform::Q2NSString;
using Platform::NSlang;
using Platform::NS2QString;
@@ -48,10 +55,11 @@ using Platform::NS2QString;
- (id)debugQuickLookObject;
@end
@end // @interface qVisualize
@implementation qVisualize {
NSString *value;
}
+ (id)bytearr:(const QByteArray &)arr {
@@ -78,59 +86,68 @@ using Platform::NS2QString;
return value;
}
@end
@end // @implementation qVisualize
@interface ApplicationDelegate : NSObject<NSApplicationDelegate> {
SPMediaKeyTap *keyTap;
BOOL watchingMediaKeys;
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (void)applicationDidBecomeActive:(NSNotification *)aNotification;
- (void)receiveWakeNote:(NSNotification*)note;
- (void)setWatchingMediaKeys:(BOOL)watching;
- (BOOL)isWatchingMediaKeys;
- (void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
- (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag;
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification;
- (void) applicationDidBecomeActive:(NSNotification *)aNotification;
- (void) receiveWakeNote:(NSNotification*)note;
@end
- (void) setWatchingMediaKeys:(bool)watching;
- (bool) isWatchingMediaKeys;
- (void) mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)event;
- (void) ignoreApplicationActivationRightNow;
@end // @interface ApplicationDelegate
ApplicationDelegate *_sharedDelegate = nil;
@implementation ApplicationDelegate {
SPMediaKeyTap *_keyTap;
bool _watchingMediaKeys;
bool _ignoreActivation;
base::Timer _ignoreActivationStop;
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag {
- (BOOL) applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag {
if (App::wnd() && App::wnd()->isHidden()) App::wnd()->showFromTray();
return YES;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
keyTap = nullptr;
watchingMediaKeys = false;
- (void) applicationDidFinishLaunching:(NSNotification *)aNotification {
_keyTap = nullptr;
_watchingMediaKeys = false;
_ignoreActivation = false;
_ignoreActivationStop.setCallback([self] {
_ignoreActivation = false;
});
#ifndef OS_MAC_STORE
if ([SPMediaKeyTap usesGlobalMediaKeyTap]) {
keyTap = [[SPMediaKeyTap alloc] initWithDelegate:self];
_keyTap = [[SPMediaKeyTap alloc] initWithDelegate:self];
} else {
LOG(("Media key monitoring disabled"));
}
#endif // else for !OS_MAC_STORE
}
- (void)applicationDidBecomeActive:(NSNotification *)aNotification {
- (void) applicationDidBecomeActive:(NSNotification *)aNotification {
if (auto messenger = Messenger::InstancePointer()) {
messenger->handleAppActivated();
if (auto window = App::wnd()) {
if (window->isHidden()) {
window->showFromTray();
if (!_ignoreActivation) {
messenger->handleAppActivated();
if (auto window = App::wnd()) {
if (window->isHidden()) {
window->showFromTray();
}
}
}
}
}
- (void)receiveWakeNote:(NSNotification*)aNotification {
- (void) receiveWakeNote:(NSNotification*)aNotification {
if (auto messenger = Messenger::InstancePointer()) {
messenger->checkLocalTime();
}
@@ -139,38 +156,43 @@ ApplicationDelegate *_sharedDelegate = nil;
Media::Player::DetachFromDeviceByTimer();
}
- (void)setWatchingMediaKeys:(BOOL)watching {
if (watchingMediaKeys != watching) {
watchingMediaKeys = watching;
if (keyTap) {
- (void) setWatchingMediaKeys:(bool)watching {
if (_watchingMediaKeys != watching) {
_watchingMediaKeys = watching;
if (_keyTap) {
#ifndef OS_MAC_STORE
if (watchingMediaKeys) {
[keyTap startWatchingMediaKeys];
if (_watchingMediaKeys) {
[_keyTap startWatchingMediaKeys];
} else {
[keyTap stopWatchingMediaKeys];
[_keyTap stopWatchingMediaKeys];
}
#endif // else for !OS_MAC_STORE
}
}
}
- (BOOL)isWatchingMediaKeys {
return watchingMediaKeys;
- (bool) isWatchingMediaKeys {
return _watchingMediaKeys;
}
- (void)mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)e {
- (void) mediaKeyTap:(SPMediaKeyTap*)keyTap receivedMediaKeyEvent:(NSEvent*)e {
if (e && [e type] == NSSystemDefined && [e subtype] == SPSystemDefinedEventMediaKeys) {
objc_handleMediaKeyEvent(e);
}
}
@end
- (void) ignoreApplicationActivationRightNow {
_ignoreActivation = true;
_ignoreActivationStop.callOnce(kIgnoreActivationTimeoutMs);
}
@end // @implementation ApplicationDelegate
namespace Platform {
void SetWatchingMediaKeys(bool watching) {
if (_sharedDelegate) {
[_sharedDelegate setWatchingMediaKeys:(watching ? YES : NO)];
[_sharedDelegate setWatchingMediaKeys:watching];
}
}
@@ -327,12 +349,19 @@ void objc_start() {
name: NSWorkspaceDidWakeNotification object: NULL];
}
void objc_ignoreApplicationActivationRightNow() {
if (_sharedDelegate) {
[_sharedDelegate ignoreApplicationActivationRightNow];
}
}
namespace {
NSURL *_downloadPathUrl = nil;
}
void objc_finish() {
[_sharedDelegate release];
_sharedDelegate = nil;
if (_downloadPathUrl) {
[_downloadPathUrl stopAccessingSecurityScopedResource];
_downloadPathUrl = nil;

View File

@@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "platform/win/windows_event_filter.h"
#include "mainwindow.h"
#include "auth_session.h"
namespace Platform {
namespace {
@@ -73,7 +74,9 @@ bool EventFilter::mainWindowEvent(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPa
switch (msg) {
case WM_TIMECHANGE: {
App::wnd()->checkAutoLockIn(100);
if (AuthSession::Exists()) {
AuthSession::Current().checkAutoLockIn(100);
}
} return false;
case WM_WTSSESSION_CHANGE: {

View File

@@ -23,6 +23,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#include "mainwindow.h"
#include "passcodewidget.h"
#include "mainwidget.h"
#include "messenger.h"
#include "media/player/media_player_instance.h"
#include "platform/platform_specific.h"
#include "base/parse_helper.h"
@@ -37,7 +38,7 @@ bool lock_telegram() {
w->passcodeWidget()->onSubmit();
return true;
} else if (Global::LocalPasscode()) {
w->setupPasscode();
Messenger::Instance().setupPasscode();
return true;
}
}

View File

@@ -42,45 +42,75 @@ void Downloader::clearPriorities() {
++_priority;
}
void Downloader::requestedAmountIncrement(MTP::DcId dcId, int index, int amount) {
Expects(index >= 0 && index < MTP::kDownloadSessionsCount);
auto it = _requestedBytesAmount.find(dcId);
if (it == _requestedBytesAmount.cend()) {
it = _requestedBytesAmount.emplace(dcId, RequestedInDc { { 0 } }).first;
}
it->second[index] += amount;
if (it->second[index]) {
Messenger::Instance().killDownloadSessionsStop(dcId);
} else {
Messenger::Instance().killDownloadSessionsStart(dcId);
}
}
int Downloader::chooseDcIndexForRequest(MTP::DcId dcId) const {
auto result = 0;
auto it = _requestedBytesAmount.find(dcId);
if (it != _requestedBytesAmount.cend()) {
for (auto i = 1; i != MTP::kDownloadSessionsCount; ++i) {
if (it->second[i] < it->second[result]) {
result = i;
}
}
}
return result;
}
Downloader::~Downloader() {
// The file loaders have pointer to downloader and they cancel
// requests in destructor where they use that pointer, so all
// of them need to be destroyed before any internal state of Downloader.
_delayedDestroyedLoaders.clear();
}
} // namespace Storage
namespace {
struct DataRequested {
DataRequested() {
memset(v, 0, sizeof(v));
}
int64 v[MTPDownloadSessionsCount];
};
QMap<int32, DataRequested> DataRequestedMap;
constexpr auto kDownloadPhotoPartSize = 64 * 1024; // 64kb for photo
constexpr auto kDownloadDocumentPartSize = 128 * 1024; // 128kb for document
constexpr auto kMaxFileQueries = 16; // max 16 file parts downloaded at the same time
constexpr auto kMaxWebFileQueries = 8; // max 8 http[s] files downloaded at the same time
} // namespace
struct FileLoaderQueue {
FileLoaderQueue(int32 limit) : limit(limit) {
FileLoaderQueue(int queriesLimit) : queriesLimit(queriesLimit) {
}
int queries = 0;
int limit = 0;
int queriesCount = 0;
int queriesLimit = 0;
FileLoader *start = nullptr;
FileLoader *end = nullptr;
};
namespace {
typedef QMap<int32, FileLoaderQueue> LoaderQueues;
LoaderQueues queues;
FileLoaderQueue _webQueue(MaxWebFileQueries);
using LoaderQueues = QMap<int32, FileLoaderQueue>;
LoaderQueues queues;
QThread *_webLoadThread = 0;
WebLoadManager *_webLoadManager = 0;
WebLoadManager *webLoadManager() {
return (_webLoadManager && _webLoadManager != FinishedWebLoadManager) ? _webLoadManager : 0;
}
WebLoadMainManager *_webLoadMainManager = 0;
FileLoaderQueue _webQueue(kMaxWebFileQueries);
QThread *_webLoadThread = nullptr;
WebLoadManager *_webLoadManager = nullptr;
WebLoadManager *webLoadManager() {
return (_webLoadManager && _webLoadManager != FinishedWebLoadManager) ? _webLoadManager : nullptr;
}
WebLoadMainManager *_webLoadMainManager = nullptr;
} // namespace
FileLoader::FileLoader(const QString &toFile, int32 size, LocationType locationType, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading)
: _downloader(&AuthSession::Current().downloader())
@@ -144,10 +174,14 @@ void FileLoader::permitLoadFromCloud() {
}
void FileLoader::loadNext() {
if (_queue->queries >= _queue->limit) return;
for (FileLoader *i = _queue->start; i;) {
if (_queue->queriesCount >= _queue->queriesLimit) {
return;
}
for (auto i = _queue->start; i;) {
if (i->loadPart()) {
if (_queue->queries >= _queue->limit) return;
if (_queue->queriesCount >= _queue->queriesLimit) {
return;
}
} else {
i = i->_next;
}
@@ -357,44 +391,46 @@ void FileLoader::cancel(bool fail) {
}
void FileLoader::startLoading(bool loadFirst, bool prior) {
if ((_queue->queries >= _queue->limit && (!loadFirst || !prior)) || _finished) return;
if ((_queue->queriesCount >= _queue->queriesLimit && (!loadFirst || !prior)) || _finished) {
return;
}
loadPart();
}
mtpFileLoader::mtpFileLoader(const StorageImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading)
: FileLoader(QString(), size, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading)
, _dc(location->dc())
, _dcId(location->dc())
, _location(location) {
auto shiftedDcId = MTP::downloadDcId(_dc, 0);
auto shiftedDcId = MTP::downloadDcId(_dcId, 0);
auto i = queues.find(shiftedDcId);
if (i == queues.cend()) {
i = queues.insert(shiftedDcId, FileLoaderQueue(MaxFileQueries));
i = queues.insert(shiftedDcId, FileLoaderQueue(kMaxFileQueries));
}
_queue = &i.value();
}
mtpFileLoader::mtpFileLoader(int32 dc, uint64 id, uint64 accessHash, int32 version, LocationType type, const QString &to, int32 size, LoadToCacheSetting toCache, LoadFromCloudSetting fromCloud, bool autoLoading)
: FileLoader(to, size, type, toCache, fromCloud, autoLoading)
, _dc(dc)
, _dcId(dc)
, _id(id)
, _accessHash(accessHash)
, _version(version) {
auto shiftedDcId = MTP::downloadDcId(_dc, 0);
auto shiftedDcId = MTP::downloadDcId(_dcId, 0);
auto i = queues.find(shiftedDcId);
if (i == queues.cend()) {
i = queues.insert(shiftedDcId, FileLoaderQueue(MaxFileQueries));
i = queues.insert(shiftedDcId, FileLoaderQueue(kMaxFileQueries));
}
_queue = &i.value();
}
mtpFileLoader::mtpFileLoader(const WebFileImageLocation *location, int32 size, LoadFromCloudSetting fromCloud, bool autoLoading)
: FileLoader(QString(), size, UnknownFileLocation, LoadToCacheAsWell, fromCloud, autoLoading)
, _dc(location->dc())
, _dcId(location->dc())
, _urlLocation(location) {
auto shiftedDcId = MTP::downloadDcId(_dc, 0);
auto shiftedDcId = MTP::downloadDcId(_dcId, 0);
auto i = queues.find(shiftedDcId);
if (i == queues.cend()) {
i = queues.insert(shiftedDcId, FileLoaderQueue(MaxFileQueries));
i = queues.insert(shiftedDcId, FileLoaderQueue(kMaxFileQueries));
}
_queue = &i.value();
}
@@ -403,58 +439,15 @@ 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 (_finished || _lastComplete || (!_dcIndexByRequest.isEmpty() && !_size)) {
if (DebugLogging::FileLoader() && _id) {
DEBUG_LOG(("FileLoader(%1): loadPart() returned, _finished=%2, _lastComplete=%3, _requests.size()=%4, _size=%5").arg(_id).arg(Logs::b(_finished)).arg(Logs::b(_lastComplete)).arg(_dcIndexByRequest.size()).arg(_size));
}
if (_finished || _lastComplete || (!_sentRequests.empty() && !_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(_dcIndexByRequest)));
}
} else if (_size && _nextRequestOffset >= _size) {
return false;
}
auto offset = _nextRequestOffset;
auto dcIndex = 0;
auto &dr = DataRequestedMap[_dc];
if (_size) {
for (auto i = 1; i != MTPDownloadSessionsCount; ++i) {
if (dr.v[i] < dr.v[dcIndex]) {
dcIndex = i;
}
}
}
App::app()->killDownloadSessionsStop(_dc);
auto requestId = makeRequest(offset, dcIndex);
_dcIndexByRequest.insert(requestId, dcIndex);
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(_dcIndexByRequest)));
}
makeRequest(_nextRequestOffset);
_nextRequestOffset += partSize();
return true;
}
@@ -465,42 +458,55 @@ int mtpFileLoader::partSize() const {
return kDownloadDocumentPartSize;
}
mtpRequestId mtpFileLoader::makeRequest(int offset, int dcIndex) {
auto limit = partSize();
DataRequestedMap[_dc].v[dcIndex] += limit;
++_queue->queries;
_nextRequestOffset += limit;
if (_urlLocation) {
return MTP::send(MTPupload_GetWebFile(MTP_inputWebFileLocation(MTP_bytes(_urlLocation->url()), MTP_long(_urlLocation->accessHash())), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::webPartLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::downloadDcId(_dc, dcIndex), 50);
}
MTPInputFileLocation loc;
if (_location) {
loc = MTP_inputFileLocation(MTP_long(_location->volume()), MTP_int(_location->local()), MTP_long(_location->secret()));
} else {
loc = MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_accessHash), MTP_int(_version));
}
return MTP::send(MTPupload_GetFile(loc, MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::normalPartLoaded, offset), rpcFail(&mtpFileLoader::partFailed), MTP::downloadDcId(_dc, dcIndex), 50);
mtpFileLoader::RequestData mtpFileLoader::prepareRequest(int offset) const {
auto result = RequestData();
result.dcId = _cdnDcId ? _cdnDcId : _dcId;
result.dcIndex = _size ? _downloader->chooseDcIndexForRequest(result.dcId) : 0;
result.offset = offset;
return result;
}
void mtpFileLoader::normalPartLoaded(int offset, const MTPupload_File &result, mtpRequestId req) {
if (result.type() != mtpc_upload_file) {
if (DebugLogging::FileLoader() && _id) {
DEBUG_LOG(("FileLoader(%1): bad cons received! %2").arg(_id).arg(result.type()));
void mtpFileLoader::makeRequest(int offset) {
auto requestData = prepareRequest(offset);
auto send = [this, &requestData] {
auto offset = requestData.offset;
auto limit = partSize();
auto shiftedDcId = MTP::downloadDcId(requestData.dcId, requestData.dcIndex);
if (_cdnDcId) {
t_assert(requestData.dcId == _cdnDcId);
return MTP::send(MTPupload_GetCdnFile(MTP_bytes(_cdnToken), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::cdnPartLoaded), rpcFail(&mtpFileLoader::cdnPartFailed), shiftedDcId, 50);
} else if (_urlLocation) {
t_assert(requestData.dcId == _dcId);
return MTP::send(MTPupload_GetWebFile(MTP_inputWebFileLocation(MTP_bytes(_urlLocation->url()), MTP_long(_urlLocation->accessHash())), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::webPartLoaded), rpcFail(&mtpFileLoader::partFailed), shiftedDcId, 50);
} else {
t_assert(requestData.dcId == _dcId);
auto location = [this] {
if (_location) {
return MTP_inputFileLocation(MTP_long(_location->volume()), MTP_int(_location->local()), MTP_long(_location->secret()));
}
return MTP_inputDocumentFileLocation(MTP_long(_id), MTP_long(_accessHash), MTP_int(_version));
};
return MTP::send(MTPupload_GetFile(location(), MTP_int(offset), MTP_int(limit)), rpcDone(&mtpFileLoader::normalPartLoaded), rpcFail(&mtpFileLoader::partFailed), shiftedDcId, 50);
}
return cancel(true);
};
placeSentRequest(send(), requestData);
}
void mtpFileLoader::normalPartLoaded(const MTPupload_File &result, mtpRequestId requestId) {
Expects(result.type() == mtpc_upload_fileCdnRedirect || result.type() == mtpc_upload_file);
auto offset = finishSentRequestGetOffset(requestId);
if (result.type() == mtpc_upload_fileCdnRedirect) {
return switchToCDN(offset, result.c_upload_fileCdnRedirect());
}
auto bytes = gsl::as_bytes(gsl::make_span(result.c_upload_file().vbytes.v));
return partLoaded(offset, bytes, req);
return partLoaded(offset, bytes);
}
void mtpFileLoader::webPartLoaded(int offset, const MTPupload_WebFile &result, mtpRequestId req) {
if (result.type() != mtpc_upload_webFile) {
if (DebugLogging::FileLoader() && _id) {
DEBUG_LOG(("FileLoader(%1): bad cons received! %2").arg(_id).arg(result.type()));
}
return cancel(true);
}
void mtpFileLoader::webPartLoaded(const MTPupload_WebFile &result, mtpRequestId requestId) {
Expects(result.type() == mtpc_upload_webFile);
auto offset = finishSentRequestGetOffset(requestId);
auto &webFile = result.c_upload_webFile();
if (!_size) {
_size = webFile.vsize.v;
@@ -509,32 +515,72 @@ void mtpFileLoader::webPartLoaded(int offset, const MTPupload_WebFile &result, m
return cancel(true);
}
auto bytes = gsl::as_bytes(gsl::make_span(webFile.vbytes.v));
return partLoaded(offset, bytes, req);
return partLoaded(offset, bytes);
}
void mtpFileLoader::partLoaded(int offset, base::const_byte_span bytes, mtpRequestId req) {
auto i = _dcIndexByRequest.find(req);
if (i == _dcIndexByRequest.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(_dcIndexByRequest)));
}
return loadNext();
void mtpFileLoader::cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequestId requestId) {
auto offset = finishSentRequestGetOffset(requestId);
if (result.type() == mtpc_upload_cdnFileReuploadNeeded) {
auto requestData = RequestData();
requestData.dcId = _dcId;
requestData.dcIndex = 0;
requestData.offset = offset;
auto shiftedDcId = MTP::downloadDcId(requestData.dcId, requestData.dcIndex);
auto requestId = MTP::send(MTPupload_ReuploadCdnFile(MTP_bytes(_cdnToken), result.c_upload_cdnFileReuploadNeeded().vrequest_token), rpcDone(&mtpFileLoader::reuploadDone), rpcFail(&mtpFileLoader::cdnPartFailed), shiftedDcId);
placeSentRequest(requestId, requestData);
return;
}
Expects(result.type() == mtpc_upload_cdnFile);
auto limit = partSize();
auto dcIndex = i.value();
DataRequestedMap[_dc].v[dcIndex] -= limit;
auto key = gsl::as_bytes(gsl::make_span(_cdnEncryptionKey));
auto iv = gsl::as_bytes(gsl::make_span(_cdnEncryptionIV));
Expects(key.size() == MTP::CTRState::KeySize);
Expects(iv.size() == MTP::CTRState::IvecSize);
--_queue->queries;
_dcIndexByRequest.erase(i);
auto state = MTP::CTRState();
auto ivec = gsl::as_writeable_bytes(gsl::make_span(state.ivec));
std::copy(iv.begin(), iv.end(), ivec.begin());
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(_dcIndexByRequest)));
}
auto counterOffset = static_cast<uint32>(offset) >> 4;
state.ivec[15] = static_cast<uchar>(counterOffset & 0xFF);
state.ivec[14] = static_cast<uchar>((counterOffset >> 8) & 0xFF);
state.ivec[13] = static_cast<uchar>((counterOffset >> 16) & 0xFF);
state.ivec[12] = static_cast<uchar>((counterOffset >> 24) & 0xFF);
auto decryptInPlace = result.c_upload_cdnFile().vbytes.v;
MTP::aesCtrEncrypt(decryptInPlace.data(), decryptInPlace.size(), key.data(), &state);
auto bytes = gsl::as_bytes(gsl::make_span(decryptInPlace));
return partLoaded(offset, bytes);
}
void mtpFileLoader::reuploadDone(const MTPBool &result, mtpRequestId requestId) {
auto offset = finishSentRequestGetOffset(requestId);
makeRequest(offset);
}
void mtpFileLoader::placeSentRequest(mtpRequestId requestId, const RequestData &requestData) {
_downloader->requestedAmountIncrement(requestData.dcId, requestData.dcIndex, partSize());
++_queue->queriesCount;
_sentRequests.emplace(requestId, requestData);
}
int mtpFileLoader::finishSentRequestGetOffset(mtpRequestId requestId) {
auto it = _sentRequests.find(requestId);
Expects(it != _sentRequests.cend());
auto requestData = it->second;
_downloader->requestedAmountIncrement(requestData.dcId, requestData.dcIndex, -partSize());
--_queue->queriesCount;
_sentRequests.erase(it);
return requestData.offset;
}
void mtpFileLoader::partLoaded(int offset, base::const_byte_span bytes) {
if (bytes.size()) {
if (_fileIsOpen) {
int64 fsize = _file.size();
auto fsize = _file.size();
if (offset < fsize) {
_skippedBytes -= bytes.size();
} else if (offset > fsize) {
@@ -566,7 +612,7 @@ void mtpFileLoader::partLoaded(int offset, base::const_byte_span bytes, mtpReque
if (!bytes.size() || (bytes.size() % 1024)) { // bad next offset
_lastComplete = true;
}
if (_dcIndexByRequest.isEmpty() && (_lastComplete || (_size && _nextRequestOffset >= _size))) {
if (_sentRequests.empty() && (_lastComplete || (_size && _nextRequestOffset >= _size))) {
if (!_fname.isEmpty() && (_toCache == LoadToCacheAsWell)) {
if (!_fileIsOpen) _fileIsOpen = _file.open(QIODevice::WriteOnly);
if (!_fileIsOpen) {
@@ -584,15 +630,11 @@ void mtpFileLoader::partLoaded(int offset, base::const_byte_span bytes, mtpReque
}
removeFromQueue();
if (!_queue->queries) {
App::app()->killDownloadSessionsStart(_dc);
}
if (_localStatus == LocalNotFound || _localStatus == LocalFailed) {
if (_urlLocation) {
Local::writeImage(storageKey(*_urlLocation), StorageImageSaved(_data));
} else if (_locationType != UnknownFileLocation) { // audio, video, document
MediaKey mkey = mediaKey(_locationType, _dc, _id, _version);
auto mkey = mediaKey(_locationType, _dcId, _id, _version);
if (!_fname.isEmpty()) {
Local::writeFileLocation(mkey, FileLocation(_fname));
}
@@ -607,10 +649,6 @@ void mtpFileLoader::partLoaded(int offset, base::const_byte_span bytes, mtpReque
Local::writeImage(storageKey(*_location), StorageImageSaved(_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(_dcIndexByRequest)));
}
}
if (_finished) {
_downloader->taskFinished().notify();
@@ -628,22 +666,59 @@ bool mtpFileLoader::partFailed(const RPCError &error) {
return true;
}
bool mtpFileLoader::cdnPartFailed(const RPCError &error, mtpRequestId requestId) {
if (MTP::isDefaultHandledError(error)) return false;
if (error.type() == qstr("FILE_TOKEN_INVALID") || error.type() == qstr("REQUEST_TOKEN_INVALID")) {
auto offset = finishSentRequestGetOffset(requestId);
changeCDNParams(offset, 0, QByteArray(), QByteArray(), QByteArray());
return true;
}
return partFailed(error);
}
void mtpFileLoader::cancelRequests() {
if (_dcIndexByRequest.isEmpty()) return;
auto limit = partSize();
DataRequested &dr(DataRequestedMap[_dc]);
for (auto i = _dcIndexByRequest.cbegin(), e = _dcIndexByRequest.cend(); i != e; ++i) {
MTP::cancel(i.key());
int32 dcIndex = i.value();
dr.v[dcIndex] -= limit;
while (!_sentRequests.empty()) {
auto requestId = _sentRequests.begin()->first;
MTP::cancel(requestId);
finishSentRequestGetOffset(requestId);
}
_queue->queries -= _dcIndexByRequest.size();
_dcIndexByRequest.clear();
}
if (!_queue->queries) {
Messenger::Instance().killDownloadSessionsStart(_dc);
void mtpFileLoader::switchToCDN(int offset, const MTPDupload_fileCdnRedirect &redirect) {
changeCDNParams(offset, redirect.vdc_id.v, redirect.vfile_token.v, redirect.vencryption_key.v, redirect.vencryption_iv.v);
}
void mtpFileLoader::changeCDNParams(int offset, MTP::DcId dcId, const QByteArray &token, const QByteArray &encryptionKey, const QByteArray &encryptionIV) {
if (dcId != 0 && (encryptionKey.size() != MTP::CTRState::KeySize || encryptionIV.size() != MTP::CTRState::IvecSize)) {
LOG(("Message Error: Wrong key (%1) / iv (%2) size in CDN params").arg(encryptionKey.size()).arg(encryptionIV.size()));
cancel(true);
return;
}
auto resendAllRequests = (_cdnDcId != dcId
|| _cdnToken != token
|| _cdnEncryptionKey != encryptionKey
|| _cdnEncryptionIV != encryptionIV);
_cdnDcId = dcId;
_cdnToken = token;
_cdnEncryptionKey = encryptionKey;
_cdnEncryptionIV = encryptionIV;
if (resendAllRequests && !_sentRequests.empty()) {
auto resendOffsets = std::vector<int>();
resendOffsets.reserve(_sentRequests.size());
while (!_sentRequests.empty()) {
auto requestId = _sentRequests.begin()->first;
MTP::cancel(requestId);
auto resendOffset = finishSentRequestGetOffset(requestId);
resendOffsets.push_back(resendOffset);
}
for (auto resendOffset : resendOffsets) {
makeRequest(offset);
}
}
makeRequest(offset);
}
bool mtpFileLoader::tryLoadLocal() {
@@ -660,7 +735,7 @@ bool mtpFileLoader::tryLoadLocal() {
_localTaskId = Local::startImageLoad(storageKey(*_location), this);
} else {
if (_toCache == LoadToCacheAsWell) {
MediaKey mkey = mediaKey(_locationType, _dc, _id, _version);
MediaKey mkey = mediaKey(_locationType, _dcId, _id, _version);
if (_locationType == DocumentFileLocation) {
_localTaskId = Local::startStickerImageLoad(mkey, this);
} else if (_locationType == AudioFileLocation) {

View File

@@ -40,6 +40,11 @@ public:
return _taskFinishedObservable;
}
void requestedAmountIncrement(MTP::DcId dcId, int index, int amount);
int chooseDcIndexForRequest(MTP::DcId dcId) const;
~Downloader();
private:
base::Observable<void> _taskFinishedObservable;
int _priority = 1;
@@ -47,6 +52,9 @@ private:
SingleQueuedInvokation _delayedLoadersDestroyer;
std::vector<std::unique_ptr<FileLoader>> _delayedDestroyedLoaders;
using RequestedInDc = std::array<int64, MTP::kDownloadSessionsCount>;
std::map<MTP::DcId, RequestedInDc> _requestedBytesAmount;
};
} // namespace Storage
@@ -134,11 +142,11 @@ signals:
protected:
void readImage(const QSize &shrinkBox) const;
Storage::Downloader *_downloader = nullptr;
gsl::not_null<Storage::Downloader*> _downloader;
FileLoader *_prev = nullptr;
FileLoader *_next = nullptr;
int _priority = 0;
FileLoaderQueue *_queue;
FileLoaderQueue *_queue = nullptr;
bool _paused = false;
bool _autoLoading = false;
@@ -198,26 +206,41 @@ public:
~mtpFileLoader();
private:
struct RequestData {
MTP::DcId dcId = 0;
int dcIndex = 0;
int offset = 0;
};
bool tryLoadLocal() override;
void cancelRequests() override;
int partSize() const;
mtpRequestId makeRequest(int offset, int dcIndex);
QMap<mtpRequestId, int> _dcIndexByRequest;
RequestData prepareRequest(int offset) const;
void makeRequest(int offset);
bool loadPart() override;
void normalPartLoaded(int offset, const MTPupload_File &result, mtpRequestId req);
void webPartLoaded(int offset, const MTPupload_WebFile &result, mtpRequestId req);
void normalPartLoaded(const MTPupload_File &result, mtpRequestId requestId);
void webPartLoaded(const MTPupload_WebFile &result, mtpRequestId requestId);
void cdnPartLoaded(const MTPupload_CdnFile &result, mtpRequestId requestId);
void reuploadDone(const MTPBool &result, mtpRequestId requestId);
void partLoaded(int offset, base::const_byte_span bytes, mtpRequestId req);
void partLoaded(int offset, base::const_byte_span bytes);
bool partFailed(const RPCError &error);
bool cdnPartFailed(const RPCError &error, mtpRequestId requestId);
void placeSentRequest(mtpRequestId requestId, const RequestData &requestData);
int finishSentRequestGetOffset(mtpRequestId requestId);
void switchToCDN(int offset, const MTPDupload_fileCdnRedirect &redirect);
void changeCDNParams(int offset, MTP::DcId dcId, const QByteArray &token, const QByteArray &encryptionKey, const QByteArray &encryptionIV);
std::map<mtpRequestId, RequestData> _sentRequests;
bool _lastComplete = false;
int32 _skippedBytes = 0;
int32 _nextRequestOffset = 0;
int32 _dc; // for photo locations
MTP::DcId _dcId = 0; // for photo locations
const StorageImageLocation *_location = nullptr;
uint64 _id = 0; // for document locations
@@ -226,6 +249,11 @@ private:
const WebFileImageLocation *_urlLocation = nullptr; // for webdocument locations
MTP::DcId _cdnDcId = 0;
QByteArray _cdnToken;
QByteArray _cdnEncryptionKey;
QByteArray _cdnEncryptionIV;
};
class webFileLoaderPrivate;

View File

@@ -20,6 +20,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
*/
#include "storage/file_upload.h"
namespace {
constexpr auto kMaxUploadFileParallelSize = MTP::kUploadSessionsCount * 512 * 1024; // max 512kb uploaded at the same time in each session
} // namespace
FileUploader::FileUploader() : sentSize(0) {
memset(sentSizes, 0, sizeof(sentSizes));
nextTimer.setSingleShot(true);
@@ -88,7 +94,7 @@ void FileUploader::currentFailed() {
dcMap.clear();
uploading = FullMsgId();
sentSize = 0;
for (int i = 0; i < MTPUploadSessionsCount; ++i) {
for (int i = 0; i < MTP::kUploadSessionsCount; ++i) {
sentSizes[i] = 0;
}
@@ -96,13 +102,13 @@ void FileUploader::currentFailed() {
}
void FileUploader::killSessions() {
for (int i = 0; i < MTPUploadSessionsCount; ++i) {
for (int i = 0; i < MTP::kUploadSessionsCount; ++i) {
MTP::stopSession(MTP::uploadDcId(i));
}
}
void FileUploader::sendNext() {
if (sentSize >= MaxUploadFileParallelSize || _paused.msg) return;
if (sentSize >= kMaxUploadFileParallelSize || _paused.msg) return;
bool killing = killSessionsTimer.isActive();
if (queue.isEmpty()) {
@@ -123,7 +129,7 @@ void FileUploader::sendNext() {
uploading = i.key();
}
int todc = 0;
for (int dc = 1; dc < MTPUploadSessionsCount; ++dc) {
for (int dc = 1; dc < MTP::kUploadSessionsCount; ++dc) {
if (sentSizes[dc] < sentSizes[todc]) {
todc = dc;
}
@@ -246,7 +252,7 @@ void FileUploader::clear() {
docRequestsSent.clear();
dcMap.clear();
sentSize = 0;
for (int32 i = 0; i < MTPUploadSessionsCount; ++i) {
for (int i = 0; i < MTP::kUploadSessionsCount; ++i) {
MTP::stopSession(MTP::uploadDcId(i));
sentSizes[i] = 0;
}

View File

@@ -130,7 +130,7 @@ private:
QMap<mtpRequestId, int32> docRequestsSent;
QMap<mtpRequestId, int32> dcMap;
uint32 sentSize;
uint32 sentSizes[MTPUploadSessionsCount];
uint32 sentSizes[MTP::kUploadSessionsCount];
FullMsgId uploading, _paused;
Queue queue;

View File

@@ -458,7 +458,8 @@ void FileLoadTask::process() {
if (video->isGifv) {
attributes.push_back(MTP_documentAttributeAnimated());
}
attributes.push_back(MTP_documentAttributeVideo(MTP_int(video->duration), MTP_int(coverWidth), MTP_int(coverHeight)));
auto flags = MTPDdocumentAttributeVideo::Flags(0);
attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(video->duration), MTP_int(coverWidth), MTP_int(coverHeight)));
auto cover = (coverWidth > 90 || coverHeight > 90)
? video->thumbnail.scaled(90, 90, Qt::KeepAspectRatio, Qt::SmoothTransformation)

View File

@@ -853,8 +853,8 @@ struct ReadSettingsContext {
MTP::DcOptions dcOptions;
};
void applyReadContext(const ReadSettingsContext &context) {
Messenger::Instance().dcOptions()->addFromOther(context.dcOptions);
void applyReadContext(ReadSettingsContext &&context) {
Messenger::Instance().dcOptions()->addFromOther(std::move(context.dcOptions));
}
bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSettingsContext &context) {
@@ -1710,7 +1710,16 @@ void _writeUserSettings() {
}
auto userDataInstance = StoredAuthSessionCache ? &StoredAuthSessionCache->data : Messenger::Instance().getAuthSessionData();
auto userData = userDataInstance ? userDataInstance->serialize() : QByteArray();
auto dialogsWidthRatio = StoredAuthSessionCache ? StoredAuthSessionCache->dialogsWidthRatio : (App::wnd() ? App::wnd()->controller()->dialogsWidthRatio().value() : Window::Controller::kDefaultDialogsWidthRatio);
auto dialogsWidthRatio = [] {
if (StoredAuthSessionCache) {
return StoredAuthSessionCache->dialogsWidthRatio;
} else if (auto window = App::wnd()) {
if (auto controller = window->controller()) {
return controller->dialogsWidthRatio().value();
}
}
return Window::Controller::kDefaultDialogsWidthRatio;
};
uint32 size = 21 * (sizeof(quint32) + sizeof(qint32));
size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark());
@@ -1755,7 +1764,7 @@ void _writeUserSettings() {
data.stream << quint32(dbiDialogsMode) << qint32(Global::DialogsModeEnabled() ? 1 : 0) << static_cast<qint32>(Global::DialogsMode());
data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0);
data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0);
data.stream << quint32(dbiDialogsWidthRatio) << qint32(snap(qRound(dialogsWidthRatio * 1000000), 0, 1000000));
data.stream << quint32(dbiDialogsWidthRatio) << qint32(snap(qRound(dialogsWidthRatio() * 1000000), 0, 1000000));
data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer());
if (!userData.isEmpty()) {
data.stream << quint32(dbiAuthSessionData) << userData;
@@ -1790,7 +1799,7 @@ void _readUserSettings() {
LOG(("App Info: could not read encrypted user settings..."));
_readOldUserSettings(true, context);
applyReadContext(context);
applyReadContext(std::move(context));
return _writeUserSettings();
}
@@ -1813,7 +1822,7 @@ void _readUserSettings() {
_readingUserSettings = false;
LOG(("App Info: encrypted user settings read."));
applyReadContext(context);
applyReadContext(std::move(context));
}
void _writeMtpData() {
@@ -1838,7 +1847,7 @@ void _readMtpData() {
if (!readEncryptedFile(mtp, toFilePart(_dataNameKey), FileOption::Safe)) {
if (LocalKey) {
_readOldMtpData(true, context);
applyReadContext(context);
applyReadContext(std::move(context));
_writeMtpData();
}
@@ -1857,7 +1866,7 @@ void _readMtpData() {
return _writeMtpData();
}
}
applyReadContext(context);
applyReadContext(std::move(context));
}
ReadMapState _readMap(const QByteArray &pass) {
@@ -2220,7 +2229,7 @@ void start() {
_readOldSettings(true, context);
_readOldUserSettings(false, context); // needed further in _readUserSettings
_readOldMtpData(false, context); // needed further in _readMtpData
applyReadContext(context);
applyReadContext(std::move(context));
return writeSettings();
}
@@ -2262,7 +2271,7 @@ void start() {
readTheme();
applyReadContext(context);
applyReadContext(std::move(context));
}
void writeSettings() {
@@ -3653,8 +3662,8 @@ void readSavedGifs() {
saved.reserve(cnt);
OrderedSet<DocumentId> read;
for (uint32 i = 0; i < cnt; ++i) {
DocumentData *document = Serialize::Document::readFromStream(gifs.version, gifs.stream);
if (!document || !document->isAnimation()) continue;
auto document = Serialize::Document::readFromStream(gifs.version, gifs.stream);
if (!document || !document->isGifv()) continue;
if (read.contains(document->id)) continue;
read.insert(document->id);

View File

@@ -118,7 +118,11 @@ DocumentData *Document::readFromStreamHelper(int streamAppVersion, QDataStream &
}
if (width > 0 && height > 0) {
if (duration >= 0) {
attributes.push_back(MTP_documentAttributeVideo(MTP_int(duration), MTP_int(width), MTP_int(height)));
auto flags = MTPDdocumentAttributeVideo::Flags(0);
if (type == RoundVideoDocument) {
flags |= MTPDdocumentAttributeVideo::Flag::f_round_message;
}
attributes.push_back(MTP_documentAttributeVideo(MTP_flags(flags), MTP_int(duration), MTP_int(width), MTP_int(height)));
} else {
attributes.push_back(MTP_documentAttributeImageSize(MTP_int(width), MTP_int(height)));
}

Some files were not shown because too many files have changed in this diff Show More