Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f619afc4c6 | ||
|
|
7ad7028880 | ||
|
|
8e241e8b78 | ||
|
|
6226cf2809 | ||
|
|
7312114b75 | ||
|
|
0ff299758a | ||
|
|
de7c886008 | ||
|
|
e3aacc8072 | ||
|
|
4fc2c18f58 | ||
|
|
8a8e101cd0 | ||
|
|
c10dee11e8 | ||
|
|
4e5c4a462b | ||
|
|
bc7139d67a | ||
|
|
bf0bf908c2 | ||
|
|
dd005d9027 | ||
|
|
670a725c53 | ||
|
|
af28e3b0d7 | ||
|
|
ca90b8b8fd | ||
|
|
7b7b9db20b | ||
|
|
96dbb38aaa | ||
|
|
1b7777e3a0 | ||
|
|
5480a63beb | ||
|
|
cbf040b4dc | ||
|
|
8eb7f1f1aa | ||
|
|
8d28d0691f | ||
|
|
7dd24a30b5 | ||
|
|
1725927aea | ||
|
|
50ea4e316e | ||
|
|
734b426518 | ||
|
|
232d3dcb54 | ||
|
|
eaf1e2b18e | ||
|
|
4b7e5750ec | ||
|
|
d4af14041c |
BIN
Telegram/Resources/icons/volume_mute.png
Normal file
BIN
Telegram/Resources/icons/volume_mute.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 147 B |
BIN
Telegram/Resources/icons/volume_mute@2x.png
Normal file
BIN
Telegram/Resources/icons/volume_mute@2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 206 B |
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -147,6 +147,7 @@ private:
|
||||
|
||||
QMap<History*, mtpRequestId> _draftsSaveRequestIds;
|
||||
base::Timer _draftsSaveTimer;
|
||||
base::Timer _quitSavingDraftsTimer;
|
||||
|
||||
OrderedSet<mtpRequestId> _stickerSetDisenableRequests;
|
||||
Stickers::Order _stickersOrder;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -52,6 +52,6 @@ void AutoLockBox::durationChanged(int seconds) {
|
||||
Local::writeUserSettings();
|
||||
Global::RefLocalPasscodeChanged().notify();
|
||||
|
||||
App::wnd()->checkAutoLock();
|
||||
AuthSession::Current().checkAutoLock();
|
||||
closeBox();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ void PasscodeBox::onSave(bool force) {
|
||||
} else {
|
||||
cSetPasscodeBadTries(0);
|
||||
Local::setPasscode(pwd.toUtf8());
|
||||
App::wnd()->checkAutoLock();
|
||||
AuthSession::Current().checkAutoLock();
|
||||
closeBox();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
495
Telegram/SourceFiles/codegen/lang/generator.cpp
Normal file
495
Telegram/SourceFiles/codegen/lang/generator.cpp
Normal 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
|
||||
59
Telegram/SourceFiles/codegen/lang/generator.h
Normal file
59
Telegram/SourceFiles/codegen/lang/generator.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop version of Telegram messaging app, see https://telegram.org
|
||||
|
||||
Telegram Desktop is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
It is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
|
||||
Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE
|
||||
Copyright (c) 2014-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
|
||||
36
Telegram/SourceFiles/codegen/lang/main.cpp
Normal file
36
Telegram/SourceFiles/codegen/lang/main.cpp
Normal 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();
|
||||
}
|
||||
87
Telegram/SourceFiles/codegen/lang/options.cpp
Normal file
87
Telegram/SourceFiles/codegen/lang/options.cpp
Normal 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
|
||||
@@ -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
|
||||
229
Telegram/SourceFiles/codegen/lang/parsed_file.cpp
Normal file
229
Telegram/SourceFiles/codegen/lang/parsed_file.cpp
Normal 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
|
||||
99
Telegram/SourceFiles/codegen/lang/parsed_file.h
Normal file
99
Telegram/SourceFiles/codegen/lang/parsed_file.h
Normal 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
|
||||
84
Telegram/SourceFiles/codegen/lang/processor.cpp
Normal file
84
Telegram/SourceFiles/codegen/lang/processor.cpp
Normal 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
|
||||
53
Telegram/SourceFiles/codegen/lang/processor.h
Normal file
53
Telegram/SourceFiles/codegen/lang/processor.h
Normal 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
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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" };
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -291,7 +291,6 @@ bool started();
|
||||
void start();
|
||||
void finish();
|
||||
|
||||
DeclareReadOnlyVar(uint64, LaunchId);
|
||||
DeclareRefVar(SingleQueuedInvokation, HandleHistoryUpdate);
|
||||
DeclareRefVar(SingleQueuedInvokation, HandleUnreadCounterUpdate);
|
||||
DeclareRefVar(SingleQueuedInvokation, HandleDelayedPeerUpdates);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -418,3 +418,7 @@ msgWaveformBar: 2px;
|
||||
msgWaveformSkip: 1px;
|
||||
msgWaveformMin: 2px;
|
||||
msgWaveformMax: 20px;
|
||||
|
||||
historyVideoMessageMute: icon {{ "volume_mute", historyFileThumbIconFg }};
|
||||
historyVideoMessageMuteSelected: icon {{ "volume_mute", historyFileThumbIconFgSelected }};
|
||||
historyVideoMessageMuteSize: 25px;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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.)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -290,6 +290,11 @@ public:
|
||||
void requestSendDelayed() {
|
||||
MTP::sendAnything();
|
||||
}
|
||||
void requestCancellingDiscard() {
|
||||
for (auto &request : _requests) {
|
||||
request.handled();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
class RequestWrap {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user