diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 994f5cd5f..3a6b73253 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -21,6 +21,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "apiwrap.h" #include "data/data_drafts.h" +#include "data/data_photo.h" +#include "data/data_web_page.h" #include "observer_peer.h" #include "lang/lang_keys.h" #include "application.h" @@ -330,7 +332,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt auto canViewMembers = channel->canViewMembers(); auto canEditStickers = channel->canEditStickers(); - channel->flagsFull = f.vflags.v; + channel->setFullFlags(f.vflags.v); auto newPhotoId = 0; if (auto photo = App::feedPhoto(f.vchat_photo)) { newPhotoId = photo->id; @@ -341,10 +343,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt Notify::peerUpdatedDelayed(channel, UpdateFlag::PhotoChanged); } if (f.has_migrated_from_chat_id()) { - if (!channel->mgInfo) { - channel->flags |= MTPDchannel::Flag::f_megagroup; - channel->flagsUpdated(); - } + channel->addFlags(MTPDchannel::Flag::f_megagroup); auto cfrom = App::chat(peerFromChat(f.vmigrated_from_chat_id)); bool updatedTo = (cfrom->migrateToPtr != channel), updatedFrom = (channel->mgInfo->migrateFromPtr != cfrom); if (updatedTo) { @@ -441,6 +440,7 @@ void ApiWrap::gotChatFull(PeerData *peer, const MTPmessages_ChatFull &result, mt void ApiWrap::gotUserFull(UserData *user, const MTPUserFull &result, mtpRequestId req) { auto &d = result.c_userFull(); + App::feedUsers(MTP_vector(1, d.vuser)); if (d.has_profile_photo()) { App::feedPhoto(d.vprofile_photo); diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index 0be3c2429..b5395fb58 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -394,8 +394,7 @@ namespace { data->inputUser = MTP_inputUser(d.vid, MTP_long(0)); data->setName(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); - data->setIsInaccessible(); - data->flags = 0; + data->setFlags(MTPDuser_ClientFlag::f_inaccessible | 0); data->setBotInfoVersion(-1); status = &emptyStatus; data->contact = -1; @@ -411,14 +410,18 @@ namespace { data = App::user(peer); auto canShareThisContact = data->canShareThisContactFast(); wasContact = data->isContact(); - if (!minimal) { - data->flags = d.vflags.v; + if (minimal) { + auto mask = 0 + | MTPDuser_ClientFlag::f_inaccessible; + data->setFlags((data->flags() & ~mask) | (d.vflags.v & mask)); + } else { + data->setFlags(d.vflags.v); if (d.is_self()) { data->input = MTP_inputPeerSelf(); data->inputUser = MTP_inputUserSelf(); } else if (!d.has_access_hash()) { - data->input = MTP_inputPeerUser(d.vid, MTP_long(data->isInaccessible() ? 0 : data->access)); - data->inputUser = MTP_inputUser(d.vid, MTP_long(data->isInaccessible() ? 0 : data->access)); + data->input = MTP_inputPeerUser(d.vid, MTP_long(data->accessHash())); + data->inputUser = MTP_inputUser(d.vid, MTP_long(data->accessHash())); } else { data->input = MTP_inputPeerUser(d.vid, d.vaccess_hash); data->inputUser = MTP_inputUser(d.vid, d.vaccess_hash); @@ -436,7 +439,6 @@ namespace { } data->setName(lang(lng_deleted), QString(), QString(), QString()); data->setPhoto(MTP_userProfilePhotoEmpty()); - data->setIsInaccessible(); status = &emptyStatus; } else { // apply first_name and last_name from minimal user only if we don't have @@ -475,7 +477,9 @@ namespace { } else { data->setPhoto(MTP_userProfilePhotoEmpty()); } - if (d.has_access_hash()) data->access = d.vaccess_hash.v; + if (d.has_access_hash()) { + data->setAccessHash(d.vaccess_hash.v); + } status = d.has_status() ? &d.vstatus : &emptyStatus; } if (!minimal) { @@ -579,12 +583,9 @@ namespace { cdata->date = d.vdate.v; if (d.has_migrated_to() && d.vmigrated_to.type() == mtpc_inputChannel) { - const auto &c(d.vmigrated_to.c_inputChannel()); - ChannelData *channel = App::channel(peerFromChannel(c.vchannel_id)); - if (!channel->mgInfo) { - channel->flags |= MTPDchannel::Flag::f_megagroup; - channel->flagsUpdated(); - } + auto &c = d.vmigrated_to.c_inputChannel(); + auto channel = App::channel(peerFromChannel(c.vchannel_id)); + channel->addFlags(MTPDchannel::Flag::f_megagroup); if (!channel->access) { channel->input = MTP_inputPeerChannel(c.vchannel_id, c.vaccess_hash); channel->inputChannel = d.vmigrated_to; @@ -615,13 +616,12 @@ namespace { } } - if (!(cdata->flags & MTPDchat::Flag::f_admins_enabled) && (d.vflags.v & MTPDchat::Flag::f_admins_enabled)) { + if (!(cdata->flags() & MTPDchat::Flag::f_admins_enabled) && (d.vflags.v & MTPDchat::Flag::f_admins_enabled)) { cdata->invalidateParticipants(); } - cdata->flags = d.vflags.v; + cdata->setFlags(d.vflags.v); cdata->count = d.vparticipants_count.v; - cdata->setIsForbidden(false); if (canEdit != cdata->canEdit()) { update.flags |= UpdateFlag::ChatCanEdit; } @@ -639,8 +639,7 @@ namespace { cdata->date = 0; cdata->count = -1; cdata->invalidateParticipants(); - cdata->flags = 0; - cdata->setIsForbidden(true); + cdata->setFlags(MTPDchat_ClientFlag::f_forbidden | 0); if (canEdit != cdata->canEdit()) { update.flags |= UpdateFlag::ChatCanEdit; } @@ -667,8 +666,13 @@ namespace { auto canAddMembers = cdata->canAddMembers(); if (minimal) { - auto mask = MTPDchannel::Flag::f_broadcast | MTPDchannel::Flag::f_verified | MTPDchannel::Flag::f_megagroup | MTPDchannel::Flag::f_democracy; - cdata->flags = (cdata->flags & ~mask) | (d.vflags.v & mask); + auto mask = 0 + | MTPDchannel::Flag::f_broadcast + | MTPDchannel::Flag::f_verified + | MTPDchannel::Flag::f_megagroup + | MTPDchannel::Flag::f_democracy + | MTPDchannel_ClientFlag::f_forbidden; + cdata->setFlags((cdata->flags() & ~mask) | (d.vflags.v & mask)); } else { if (d.has_admin_rights()) { cdata->setAdminRights(d.vadmin_rights); @@ -691,14 +695,12 @@ namespace { } else { cdata->setRestrictionReason(QString()); } - cdata->flags = d.vflags.v; + cdata->setFlags(d.vflags.v); } - cdata->flagsUpdated(); QString uname = d.has_username() ? TextUtilities::SingleLine(qs(d.vusername)) : QString(); cdata->setName(qs(d.vtitle), uname); - cdata->setIsForbidden(false); cdata->setPhoto(d.vphoto); if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn; @@ -722,8 +724,7 @@ namespace { cdata->inputChannel = MTP_inputChannel(d.vid, d.vaccess_hash); auto mask = mtpCastFlags(MTPDchannelForbidden::Flag::f_broadcast | MTPDchannelForbidden::Flag::f_megagroup); - cdata->flags = (cdata->flags & ~mask) | (mtpCastFlags(d.vflags) & mask); - cdata->flagsUpdated(); + cdata->setFlags((cdata->flags() & ~mask) | (mtpCastFlags(d.vflags) & mask) | MTPDchannel_ClientFlag::f_forbidden); if (cdata->hasAdminRights()) { cdata->setAdminRights(MTP_channelAdminRights(MTP_flags(0))); @@ -738,7 +739,6 @@ namespace { cdata->setPhoto(MTP_chatPhotoEmpty()); cdata->date = 0; cdata->setMembersCount(0); - cdata->setIsForbidden(true); if (wasInChannel != cdata->amIn()) update.flags |= UpdateFlag::ChannelAmIn; if (canViewAdmins != cdata->canViewAdmins() @@ -795,7 +795,7 @@ namespace { int32 pversion = chat->participants.isEmpty() ? 1 : (chat->participants.begin().value() + 1); chat->invitedByMe.clear(); chat->admins.clear(); - chat->flags &= ~MTPDchat::Flag::f_admin; + chat->removeFlags(MTPDchat::Flag::f_admin); for (auto i = v.cbegin(), e = v.cend(); i != e; ++i) { int32 uid = 0, inviter = 0; switch (i->type()) { @@ -826,7 +826,7 @@ namespace { if (i->type() == mtpc_chatParticipantAdmin) { chat->admins.insert(user); if (user->isSelf()) { - chat->flags |= MTPDchat::Flag::f_admin; + chat->addFlags(MTPDchat::Flag::f_admin); } } } else { @@ -927,7 +927,7 @@ namespace { chat->invitedByMe.remove(user); chat->admins.remove(user); if (user->isSelf()) { - chat->flags &= ~MTPDchat::Flag::f_admin; + chat->removeFlags(MTPDchat::Flag::f_admin); } History *h = App::historyLoaded(chat->id); @@ -967,9 +967,9 @@ namespace { auto badVersion = (chat->version + 1 < d.vversion.v); chat->version = d.vversion.v; if (mtpIsTrue(d.venabled)) { - chat->flags |= MTPDchat::Flag::f_admins_enabled; + chat->addFlags(MTPDchat::Flag::f_admins_enabled); } else { - chat->flags &= ~MTPDchat::Flag::f_admins_enabled; + chat->removeFlags(MTPDchat::Flag::f_admins_enabled); } if (badVersion || mtpIsTrue(d.venabled)) { chat->invalidateParticipants(); @@ -999,7 +999,7 @@ namespace { if (user) { if (mtpIsTrue(d.vis_admin)) { if (user->isSelf()) { - chat->flags |= MTPDchat::Flag::f_admin; + chat->addFlags(MTPDchat::Flag::f_admin); } if (chat->noParticipantInfo()) { Auth().api().requestFullPeer(chat); @@ -1008,7 +1008,7 @@ namespace { } } else { if (user->isSelf()) { - chat->flags &= ~MTPDchat::Flag::f_admin; + chat->removeFlags(MTPDchat::Flag::f_admin); } chat->admins.remove(user); } diff --git a/Telegram/SourceFiles/base/flags.h b/Telegram/SourceFiles/base/flags.h index d51e84f1a..5ede8265b 100644 --- a/Telegram/SourceFiles/base/flags.h +++ b/Telegram/SourceFiles/base/flags.h @@ -22,12 +22,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include -#if defined _MSC_VER && _MSC_VER < 1910 -#define FLAGS_CONSTEXPR -#else // MSVS2015 -#define FLAGS_CONSTEXPR constexpr -#endif // MSVS2015 - namespace base { template @@ -69,9 +63,11 @@ class flags { constexpr flags() = default; constexpr flags(details::flags_zero_helper) noexcept { } - constexpr flags(Enum value) noexcept : _value(static_cast(value)) { + constexpr flags(Enum value) noexcept + : _value(static_cast(value)) { } - explicit constexpr flags(Type value) noexcept : _value(value) { + static constexpr flags from_raw(Type value) noexcept { + return flags(static_cast(value)); } constexpr auto value() const noexcept { @@ -81,40 +77,40 @@ class flags { return value(); } - FLAGS_CONSTEXPR auto &operator|=(flags b) noexcept { + constexpr auto &operator|=(flags b) noexcept { _value |= b.value(); return *this; } - FLAGS_CONSTEXPR auto &operator&=(flags b) noexcept { + constexpr auto &operator&=(flags b) noexcept { _value &= b.value(); return *this; } - FLAGS_CONSTEXPR auto &operator^=(flags b) noexcept { + constexpr auto &operator^=(flags b) noexcept { _value ^= b.value(); return *this; } - FLAGS_CONSTEXPR auto operator~() const noexcept { - return flags(~value()); + constexpr auto operator~() const noexcept { + return from_raw(~value()); } - FLAGS_CONSTEXPR auto operator|(flags b) const noexcept { + constexpr auto operator|(flags b) const noexcept { return (flags(*this) |= b); } - FLAGS_CONSTEXPR auto operator&(flags b) const noexcept { + constexpr auto operator&(flags b) const noexcept { return (flags(*this) &= b); } - FLAGS_CONSTEXPR auto operator^(flags b) const noexcept { + constexpr auto operator^(flags b) const noexcept { return (flags(*this) ^= b); } - FLAGS_CONSTEXPR auto operator|(Enum b) const noexcept { + constexpr auto operator|(Enum b) const noexcept { return (flags(*this) |= b); } - FLAGS_CONSTEXPR auto operator&(Enum b) const noexcept { + constexpr auto operator&(Enum b) const noexcept { return (flags(*this) &= b); } - FLAGS_CONSTEXPR auto operator^(Enum b) const noexcept { + constexpr auto operator^(Enum b) const noexcept { return (flags(*this) ^= b); } @@ -296,8 +292,6 @@ inline constexpr auto operator>=(ExtendedEnum a, flags::value>, typename = std::enable_if_t> diff --git a/Telegram/SourceFiles/boxes/connection_box.cpp b/Telegram/SourceFiles/boxes/connection_box.cpp index e3ca06503..d776a2fd1 100644 --- a/Telegram/SourceFiles/boxes/connection_box.cpp +++ b/Telegram/SourceFiles/boxes/connection_box.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "boxes/connection_box.h" +#include "data/data_photo.h" +#include "data/data_document.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" #include "storage/localstorage.h" diff --git a/Telegram/SourceFiles/boxes/sticker_set_box.cpp b/Telegram/SourceFiles/boxes/sticker_set_box.cpp index 9bb4cd564..6240d8752 100644 --- a/Telegram/SourceFiles/boxes/sticker_set_box.cpp +++ b/Telegram/SourceFiles/boxes/sticker_set_box.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "boxes/sticker_set_box.h" +#include "data/data_document.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "mainwindow.h" diff --git a/Telegram/SourceFiles/boxes/stickers_box.cpp b/Telegram/SourceFiles/boxes/stickers_box.cpp index 54fe058c7..61d076d57 100644 --- a/Telegram/SourceFiles/boxes/stickers_box.cpp +++ b/Telegram/SourceFiles/boxes/stickers_box.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "stickers_box.h" +#include "data/data_document.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "chat_helpers/stickers.h" diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 2a7aa37a9..507ba7d03 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "calls/calls_panel.h" +#include "data/data_photo.h" #include "calls/calls_emoji_fingerprint.h" #include "styles/style_calls.h" #include "styles/style_history.h" diff --git a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp index f24cd52fa..cb0ddba39 100644 --- a/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp +++ b/Telegram/SourceFiles/chat_helpers/field_autocomplete.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "chat_helpers/field_autocomplete.h" +#include "data/data_document.h" #include "mainwindow.h" #include "apiwrap.h" #include "storage/localstorage.h" diff --git a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp index 65c9f338c..7d42c76f5 100644 --- a/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/gifs_list_widget.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "chat_helpers/gifs_list_widget.h" +#include "data/data_photo.h" +#include "data/data_document.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/buttons.h" #include "ui/widgets/input_fields.h" diff --git a/Telegram/SourceFiles/chat_helpers/stickers.cpp b/Telegram/SourceFiles/chat_helpers/stickers.cpp index f2bcc85d3..d422d7bdd 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "stickers.h" +#include "data/data_document.h" #include "boxes/stickers_box.h" #include "boxes/confirm_box.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp index e65b0846d..dce205bad 100644 --- a/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp +++ b/Telegram/SourceFiles/chat_helpers/stickers_list_widget.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "chat_helpers/stickers_list_widget.h" +#include "data/data_document.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/buttons.h" #include "ui/effects/ripple_animation.h" diff --git a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py index 2e1a684ae..f2ea401d6 100644 --- a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py +++ b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py @@ -311,16 +311,16 @@ def addChildParentFlags(child, parent): prmsInit = []; prmsNames = []; if (hasFlags != ''): - funcsText += '\tenum class Flag : int32 {\n'; + funcsText += '\tenum class Flag : uint32 {\n'; maxbit = 0; parentFlagsCheck['MTP' + name] = {}; for paramName in conditionsList: - funcsText += '\t\tf_' + paramName + ' = (1 << ' + conditions[paramName] + '),\n'; + funcsText += '\t\tf_' + paramName + ' = (1U << ' + conditions[paramName] + '),\n'; parentFlagsCheck['MTP' + name][paramName] = conditions[paramName]; maxbit = max(maxbit, int(conditions[paramName])); if (maxbit > 0): funcsText += '\n'; - funcsText += '\t\tMAX_FIELD = (1 << ' + str(maxbit) + '),\n'; + funcsText += '\t\tMAX_FIELD = (1U << ' + str(maxbit) + '),\n'; funcsText += '\t};\n'; funcsText += '\tusing Flags = base::flags;\n'; funcsText += '\tfriend inline constexpr auto is_flag_type(Flag) { return true; };\n'; @@ -453,9 +453,9 @@ def addTextSerialize(lst, dct, dataLetter): conditions = data[6]; trivialConditions = data[7]; - result += 'void Serialize_' + name + '(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {\n'; + result += 'void Serialize_' + name + '(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n'; if (len(conditions)): - result += '\tMTP' + dataLetter + name + '::Flags flag(iflag);\n\n'; + result += '\tauto flag = MTP' + dataLetter + name + '::Flags::from_raw(iflag);\n\n'; if (len(prms)): result += '\tif (stage) {\n'; result += '\t\tto.add(",\\n").addSpaces(lev);\n'; @@ -592,16 +592,16 @@ def addTextSerializeInit(lst, dct): writeText = ''; if (hasFlags != ''): - dataText += '\tenum class Flag : int32 {\n'; + dataText += '\tenum class Flag : uint32 {\n'; maxbit = 0; parentFlagsCheck['MTPD' + name] = {}; for paramName in conditionsList: - dataText += '\t\tf_' + paramName + ' = (1 << ' + conditions[paramName] + '),\n'; + dataText += '\t\tf_' + paramName + ' = (1U << ' + conditions[paramName] + '),\n'; parentFlagsCheck['MTPD' + name][paramName] = conditions[paramName]; maxbit = max(maxbit, int(conditions[paramName])); if (maxbit > 0): dataText += '\n'; - dataText += '\t\tMAX_FIELD = (1 << ' + str(maxbit) + '),\n'; + dataText += '\t\tMAX_FIELD = (1U << ' + str(maxbit) + '),\n'; dataText += '\t};\n'; dataText += '\tusing Flags = base::flags;\n'; dataText += '\tfriend inline constexpr auto is_flag_type(Flag) { return true; };\n'; @@ -853,7 +853,7 @@ def addTextSerializeInit(lst, dct): # manual types added here textSerializeMethods += '\ -void _serialize_rpc_result(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {\n\ +void _serialize_rpc_result(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n\ if (stage) {\n\ to.add(",\\n").addSpaces(lev);\n\ } else {\n\ @@ -867,7 +867,7 @@ def addTextSerializeInit(lst, dct): }\n\ }\n\ \n\ -void _serialize_msg_container(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {\n\ +void _serialize_msg_container(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n\ if (stage) {\n\ to.add(",\\n").addSpaces(lev);\n\ } else {\n\ @@ -880,7 +880,7 @@ def addTextSerializeInit(lst, dct): }\n\ }\n\ \n\ -void _serialize_core_message(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag) {\n\ +void _serialize_core_message(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag) {\n\ if (stage) {\n\ to.add(",\\n").addSpaces(lev);\n\ } else {\n\ @@ -1012,7 +1012,7 @@ class TypeCreator {\n\ ' + textSerializeMethods + '\n\ namespace {\n\ \n\ -using TextSerializer = void (*)(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, int32 iflag);\n\ +using TextSerializer = void (*)(MTPStringLogger &to, int32 stage, int32 lev, Types &types, Types &vtypes, StagesFlags &stages, StagesFlags &flags, const mtpPrime *start, const mtpPrime *end, uint32 iflag);\n\ using TextSerializers = QMap;\n\ \n\ QMap createTextSerializers() {\n\ diff --git a/Telegram/SourceFiles/data/data_document.cpp b/Telegram/SourceFiles/data/data_document.cpp new file mode 100644 index 000000000..7e195f0b7 --- /dev/null +++ b/Telegram/SourceFiles/data/data_document.cpp @@ -0,0 +1,951 @@ +/* +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 "data/data_document.h" + +#include "lang/lang_keys.h" +#include "inline_bots/inline_bot_layout_item.h" +//#include "observer_peer.h" +#include "mainwidget.h" +//#include "application.h" +//#include "storage/file_upload.h" +//#include "mainwindow.h" +#include "core/file_utilities.h" +//#include "apiwrap.h" +//#include "boxes/confirm_box.h" +#include "media/media_audio.h" +#include "storage/localstorage.h" +#include "platform/platform_specific.h" +#include "history/history_media_types.h" +//#include "styles/style_history.h" +//#include "window/themes/window_theme.h" +//#include "auth_session.h" +#include "messenger.h" +//#include "storage/file_download.h" + +QString joinList(const QStringList &list, const QString &sep) { + QString result; + if (list.isEmpty()) return result; + + int32 l = list.size(), s = sep.size() * (l - 1); + for (int32 i = 0; i < l; ++i) { + s += list.at(i).size(); + } + result.reserve(s); + result.append(list.at(0)); + for (int32 i = 1; i < l; ++i) { + result.append(sep).append(list.at(i)); + } + return result; +} + +QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir) { +#ifdef Q_OS_WIN + name = name.replace(QRegularExpression(qsl("[\\\\\\/\\:\\*\\?\\\"\\<\\>\\|]")), qsl("_")); +#elif defined Q_OS_MAC + name = name.replace(QRegularExpression(qsl("[\\:]")), qsl("_")); +#elif defined Q_OS_LINUX + name = name.replace(QRegularExpression(qsl("[\\/]")), qsl("_")); +#endif + if (Global::AskDownloadPath() || savingAs) { + if (!name.isEmpty() && name.at(0) == QChar::fromLatin1('.')) { + name = filedialogDefaultName(prefix, name); + } else if (dir.path() != qsl(".")) { + QString path = dir.absolutePath(); + if (path != cDialogLastPath()) { + cSetDialogLastPath(path); + Local::writeUserSettings(); + } + } + + // check if extension of filename is present in filter + // it should be in first filter section on the first place + // place it there, if it is not + QString ext = QFileInfo(name).suffix(), fil = filter, sep = qsl(";;"); + if (!ext.isEmpty()) { + if (QRegularExpression(qsl("^[a-zA-Z_0-9]+$")).match(ext).hasMatch()) { + QStringList filters = filter.split(sep); + if (filters.size() > 1) { + QString first = filters.at(0); + int32 start = first.indexOf(qsl("(*.")); + if (start >= 0) { + if (!QRegularExpression(qsl("\\(\\*\\.") + ext + qsl("[\\)\\s]"), QRegularExpression::CaseInsensitiveOption).match(first).hasMatch()) { + QRegularExpressionMatch m = QRegularExpression(qsl(" \\*\\.") + ext + qsl("[\\)\\s]"), QRegularExpression::CaseInsensitiveOption).match(first); + if (m.hasMatch() && m.capturedStart() > start + 3) { + int32 oldpos = m.capturedStart(), oldend = m.capturedEnd(); + fil = first.mid(0, start + 3) + ext + qsl(" *.") + first.mid(start + 3, oldpos - start - 3) + first.mid(oldend - 1) + sep + joinList(filters.mid(1), sep); + } else { + fil = first.mid(0, start + 3) + ext + qsl(" *.") + first.mid(start + 3) + sep + joinList(filters.mid(1), sep); + } + } + } else { + fil = QString(); + } + } else { + fil = QString(); + } + } else { + fil = QString(); + } + } + return filedialogGetSaveFile(name, title, fil, name) ? name : QString(); + } + + QString path; + if (Global::DownloadPath().isEmpty()) { + path = psDownloadPath(); + } else if (Global::DownloadPath() == qsl("tmp")) { + path = cTempDir(); + } else { + path = Global::DownloadPath(); + } + if (name.isEmpty()) name = qsl(".unknown"); + if (name.at(0) == QChar::fromLatin1('.')) { + if (!QDir().exists(path)) QDir().mkpath(path); + return filedialogDefaultName(prefix, name, path); + } + if (dir.path() != qsl(".")) { + path = dir.absolutePath() + '/'; + } + + QString nameStart, extension; + int32 extPos = name.lastIndexOf('.'); + if (extPos >= 0) { + nameStart = name.mid(0, extPos); + extension = name.mid(extPos); + } else { + nameStart = name; + } + QString nameBase = path + nameStart; + name = nameBase + extension; + for (int i = 0; QFileInfo(name).exists(); ++i) { + name = nameBase + QString(" (%1)").arg(i + 2) + extension; + } + + if (!QDir().exists(path)) QDir().mkpath(path); + return name; +} + +bool StickerData::setInstalled() const { + switch (set.type()) { + case mtpc_inputStickerSetID: { + auto it = Global::StickerSets().constFind(set.c_inputStickerSetID().vid.v); + return (it != Global::StickerSets().cend()) && !(it->flags & MTPDstickerSet::Flag::f_archived) && (it->flags & MTPDstickerSet::Flag::f_installed); + } break; + case mtpc_inputStickerSetShortName: { + auto name = qs(set.c_inputStickerSetShortName().vshort_name).toLower(); + for (auto it = Global::StickerSets().cbegin(), e = Global::StickerSets().cend(); it != e; ++it) { + if (it->shortName.toLower() == name) { + return !(it->flags & MTPDstickerSet::Flag::f_archived) && (it->flags & MTPDstickerSet::Flag::f_installed); + } + } + } break; + } + return false; +} + +QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = false, const QString already = QString(), const QDir &dir = QDir()) { + auto alreadySavingFilename = data->loadingFilePath(); + if (!alreadySavingFilename.isEmpty()) { + return alreadySavingFilename; + } + + QString name, filter, caption, prefix; + MimeType mimeType = mimeTypeForName(data->mime); + QStringList p = mimeType.globPatterns(); + QString pattern = p.isEmpty() ? QString() : p.front(); + if (data->voice()) { + bool mp3 = (data->mime == qstr("audio/mp3")); + name = already.isEmpty() ? (mp3 ? qsl(".mp3") : qsl(".ogg")) : already; + filter = mp3 ? qsl("MP3 Audio (*.mp3);;") : qsl("OGG Opus Audio (*.ogg);;"); + filter += FileDialog::AllFilesFilter(); + caption = lang(lng_save_audio); + prefix = qsl("audio"); + } else if (data->isVideo()) { + name = already.isEmpty() ? data->name : already; + if (name.isEmpty()) { + name = pattern.isEmpty() ? qsl(".mov") : pattern.replace('*', QString()); + } + if (pattern.isEmpty()) { + filter = qsl("MOV Video (*.mov);;") + FileDialog::AllFilesFilter(); + } else { + filter = mimeType.filterString() + qsl(";;") + FileDialog::AllFilesFilter(); + } + caption = lang(lng_save_video); + prefix = qsl("video"); + } else { + name = already.isEmpty() ? data->name : already; + if (name.isEmpty()) { + name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); + } + if (pattern.isEmpty()) { + filter = QString(); + } else { + filter = mimeType.filterString() + qsl(";;") + FileDialog::AllFilesFilter(); + } + caption = lang(data->song() ? lng_save_audio_file : lng_save_file); + prefix = qsl("doc"); + } + + return saveFileName(caption, filter, prefix, name, forceSavingAs, dir); +} + +void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, ActionOnLoad action) { + if (!data->date) return; + + auto msgId = context ? context->fullId() : FullMsgId(); + bool playVoice = data->voice(); + bool playMusic = data->tryPlaySong(); + bool playVideo = data->isVideo(); + bool playAnimation = data->isAnimation(); + auto &location = data->location(true); + if (auto applyTheme = data->isTheme()) { + if (!location.isEmpty() && location.accessEnable()) { + Messenger::Instance().showDocument(data, context); + location.accessDisable(); + return; + } + } + if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) { + using State = Media::Player::State; + if (playVoice) { + auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); + if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) { + if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { + Media::Player::mixer()->resume(state.id); + } else { + Media::Player::mixer()->pause(state.id); + } + } else { + auto audio = AudioMsgId(data, msgId); + Media::Player::mixer()->play(audio); + Media::Player::Updated().notify(audio); + if (App::main()) { + App::main()->mediaMarkRead(data); + } + } + } else if (playMusic) { + auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); + if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) { + if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { + Media::Player::mixer()->resume(state.id); + } else { + Media::Player::mixer()->pause(state.id); + } + } else { + auto song = AudioMsgId(data, msgId); + Media::Player::mixer()->play(song); + Media::Player::Updated().notify(song); + } + } else if (playVideo) { + if (!data->data().isEmpty()) { + Messenger::Instance().showDocument(data, context); + } else if (location.accessEnable()) { + Messenger::Instance().showDocument(data, context); + location.accessDisable(); + } else { + auto filepath = location.name(); + if (documentIsValidMediaFile(filepath)) { + File::Launch(filepath); + } + } + if (App::main()) App::main()->mediaMarkRead(data); + } else if (data->voice() || data->song() || data->isVideo()) { + auto filepath = location.name(); + if (documentIsValidMediaFile(filepath)) { + File::Launch(filepath); + } + if (App::main()) App::main()->mediaMarkRead(data); + } else if (data->size < App::kImageSizeLimit) { + if (!data->data().isEmpty() && playAnimation) { + if (action == ActionOnLoadPlayInline && context && context->getMedia()) { + context->getMedia()->playInline(); + } else { + Messenger::Instance().showDocument(data, context); + } + } else if (location.accessEnable()) { + if (data->isAnimation() || QImageReader(location.name()).canRead()) { + if (action == ActionOnLoadPlayInline && context && context->getMedia()) { + context->getMedia()->playInline(); + } else { + Messenger::Instance().showDocument(data, context); + } + } else { + File::Launch(location.name()); + } + location.accessDisable(); + } else { + File::Launch(location.name()); + } + } else { + File::Launch(location.name()); + } + return; + } + + if (data->status != FileReady) return; + + QString filename; + if (!data->saveToCache()) { + filename = documentSaveFilename(data); + if (filename.isEmpty()) return; + } + + data->save(filename, action, msgId); +} + +void DocumentOpenClickHandler::onClickImpl() const { + auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr); + doOpen(document(), item, document()->voice() ? ActionOnLoadNone : ActionOnLoadOpen); +} + +void GifOpenClickHandler::onClickImpl() const { + auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr); + doOpen(document(), item, ActionOnLoadPlayInline); +} + +void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) { + if (!data->date) return; + + auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs); + if (!filepath.isEmpty() && !forceSavingAs) { + File::OpenWith(filepath, QCursor::pos()); + } else { + auto fileinfo = QFileInfo(filepath); + auto filedir = filepath.isEmpty() ? QDir() : fileinfo.dir(); + auto filename = filepath.isEmpty() ? QString() : fileinfo.fileName(); + auto newfname = documentSaveFilename(data, forceSavingAs, filename, filedir); + if (!newfname.isEmpty()) { + auto action = (filename.isEmpty() || forceSavingAs) ? ActionOnLoadNone : ActionOnLoadOpenWith; + auto actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); + data->save(newfname, action, actionMsgId); + } + } +} + +void DocumentSaveClickHandler::onClickImpl() const { + doSave(document()); +} + +void DocumentCancelClickHandler::onClickImpl() const { + auto data = document(); + if (!data->date) return; + + if (data->uploading()) { + if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) { + if (auto media = item->getMedia()) { + if (media->getDocument() == data) { + App::contextItem(item); + App::main()->cancelUploadLayer(); + } + } + } + } else { + data->cancel(); + } +} + +VoiceData::~VoiceData() { + if (!waveform.isEmpty() && waveform.at(0) == -1 && waveform.size() > int32(sizeof(TaskId))) { + TaskId taskId = 0; + memcpy(&taskId, waveform.constData() + 1, sizeof(taskId)); + Local::cancelTask(taskId); + } +} + +DocumentData::DocumentData(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QString &url, const QVector &attributes) +: id(id) +, _dc(dc) +, _access(accessHash) +, _version(version) +, _url(url) { + setattributes(attributes); + if (_dc && _access) { + _location = Local::readFileLocation(mediaKey()); + } +} + +DocumentData *DocumentData::create(DocumentId id) { + return new DocumentData(id, 0, 0, 0, QString(), QVector()); +} + +DocumentData *DocumentData::create(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QVector &attributes) { + return new DocumentData(id, dc, accessHash, version, QString(), attributes); +} + +DocumentData *DocumentData::create(DocumentId id, const QString &url, const QVector &attributes) { + return new DocumentData(id, 0, 0, 0, url, attributes); +} + +void DocumentData::setattributes(const QVector &attributes) { + for (int32 i = 0, l = attributes.size(); i < l; ++i) { + switch (attributes[i].type()) { + case mtpc_documentAttributeImageSize: { + auto &d = attributes[i].c_documentAttributeImageSize(); + dimensions = QSize(d.vw.v, d.vh.v); + } break; + case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument || type == VideoDocument) { + type = AnimatedDocument; + _additional = nullptr; + } break; + case mtpc_documentAttributeSticker: { + auto &d = attributes[i].c_documentAttributeSticker(); + if (type == FileDocument) { + type = StickerDocument; + _additional = std::make_unique(); + } + if (sticker()) { + sticker()->alt = qs(d.valt); + if (sticker()->set.type() != mtpc_inputStickerSetID || d.vstickerset.type() == mtpc_inputStickerSetID) { + sticker()->set = d.vstickerset; + } + } + } break; + case mtpc_documentAttributeVideo: { + auto &d = attributes[i].c_documentAttributeVideo(); + if (type == FileDocument) { + type = d.is_round_message() ? RoundVideoDocument : VideoDocument; + } + _duration = d.vduration.v; + dimensions = QSize(d.vw.v, d.vh.v); + } break; + case mtpc_documentAttributeAudio: { + auto &d = attributes[i].c_documentAttributeAudio(); + if (type == FileDocument) { + if (d.is_voice()) { + type = VoiceDocument; + _additional = std::make_unique(); + } else { + type = SongDocument; + _additional = std::make_unique(); + } + } + if (voice()) { + voice()->duration = d.vduration.v; + VoiceWaveform waveform = documentWaveformDecode(qba(d.vwaveform)); + uchar wavemax = 0; + for (int32 i = 0, l = waveform.size(); i < l; ++i) { + uchar waveat = waveform.at(i); + if (wavemax < waveat) wavemax = waveat; + } + voice()->waveform = waveform; + voice()->wavemax = wavemax; + } else if (song()) { + song()->duration = d.vduration.v; + song()->title = qs(d.vtitle); + song()->performer = qs(d.vperformer); + } + } break; + case mtpc_documentAttributeFilename: name = qs(attributes[i].c_documentAttributeFilename().vfile_name); break; + } + } + if (type == StickerDocument) { + if (dimensions.width() <= 0 + || dimensions.height() <= 0 + || dimensions.width() > StickerMaxSize + || dimensions.height() > StickerMaxSize + || !saveToCache()) { + type = FileDocument; + _additional = nullptr; + } + } +} + +bool DocumentData::saveToCache() const { + return (type == StickerDocument && size < Storage::kMaxStickerInMemory) + || (isAnimation() && size < Storage::kMaxAnimationInMemory) + || (voice() && size < Storage::kMaxVoiceInMemory); +} + +void DocumentData::forget() { + thumb->forget(); + if (sticker()) sticker()->img->forget(); + replyPreview->forget(); + _data.clear(); +} + +void DocumentData::automaticLoad(const HistoryItem *item) { + if (loaded() || status != FileReady) return; + + if (saveToCache() && _loader != CancelledMtpFileLoader) { + if (type == StickerDocument) { + save(QString(), _actionOnLoad, _actionOnLoadMsgId); + } else if (isAnimation()) { + bool loadFromCloud = false; + if (item) { + if (item->history()->peer->isUser()) { + loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate); + } else { + loadFromCloud = !(cAutoDownloadGif() & dbiadNoGroups); + } + } else { // if load at least anywhere + loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate) || !(cAutoDownloadGif() & dbiadNoGroups); + } + save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); + } else if (voice()) { + if (item) { + bool loadFromCloud = false; + if (item->history()->peer->isUser()) { + loadFromCloud = !(cAutoDownloadAudio() & dbiadNoPrivate); + } else { + loadFromCloud = !(cAutoDownloadAudio() & dbiadNoGroups); + } + save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); + } + } + } +} + +void DocumentData::automaticLoadSettingsChanged() { + if (loaded() || status != FileReady || (!isAnimation() && !voice()) || !saveToCache() || _loader != CancelledMtpFileLoader) { + return; + } + _loader = nullptr; +} + +void DocumentData::performActionOnLoad() { + if (_actionOnLoad == ActionOnLoadNone) return; + + auto loc = location(true); + auto already = loc.name(); + auto item = _actionOnLoadMsgId.msg ? App::histItemById(_actionOnLoadMsgId) : nullptr; + auto showImage = !isVideo() && (size < App::kImageSizeLimit); + auto playVoice = voice() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen); + auto playMusic = tryPlaySong() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen); + auto playAnimation = isAnimation() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && showImage && item && item->getMedia(); + if (auto applyTheme = isTheme()) { + if (!loc.isEmpty() && loc.accessEnable()) { + Messenger::Instance().showDocument(this, item); + loc.accessDisable(); + return; + } + } + using State = Media::Player::State; + if (playVoice) { + if (loaded()) { + auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); + if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) { + if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { + Media::Player::mixer()->resume(state.id); + } else { + Media::Player::mixer()->pause(state.id); + } + } else if (Media::Player::IsStopped(state.state)) { + Media::Player::mixer()->play(AudioMsgId(this, _actionOnLoadMsgId)); + if (App::main()) App::main()->mediaMarkRead(this); + } + } + } else if (playMusic) { + if (loaded()) { + auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); + if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) { + if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { + Media::Player::mixer()->resume(state.id); + } else { + Media::Player::mixer()->pause(state.id); + } + } else if (Media::Player::IsStopped(state.state)) { + auto song = AudioMsgId(this, _actionOnLoadMsgId); + Media::Player::mixer()->play(song); + Media::Player::Updated().notify(song); + } + } + } else if (playAnimation) { + if (loaded()) { + if (_actionOnLoad == ActionOnLoadPlayInline && item->getMedia()) { + item->getMedia()->playInline(); + } else { + Messenger::Instance().showDocument(this, item); + } + } + } else { + if (already.isEmpty()) return; + + if (_actionOnLoad == ActionOnLoadOpenWith) { + File::OpenWith(already, QCursor::pos()); + } else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) { + if (voice() || song() || isVideo()) { + if (documentIsValidMediaFile(already)) { + File::Launch(already); + } + if (App::main()) App::main()->mediaMarkRead(this); + } else if (loc.accessEnable()) { + if (showImage && QImageReader(loc.name()).canRead()) { + if (_actionOnLoad == ActionOnLoadPlayInline && item && item->getMedia()) { + item->getMedia()->playInline(); + } else { + Messenger::Instance().showDocument(this, item); + } + } else { + File::Launch(already); + } + loc.accessDisable(); + } else { + File::Launch(already); + } + } + } + _actionOnLoad = ActionOnLoadNone; +} + +bool DocumentData::loaded(FilePathResolveType type) const { + if (loading() && _loader->finished()) { + if (_loader->cancelled()) { + destroyLoaderDelayed(CancelledMtpFileLoader); + } else { + auto that = const_cast(this); + that->_location = FileLocation(_loader->fileName()); + that->_data = _loader->bytes(); + if (that->sticker() && !_loader->imagePixmap().isNull()) { + that->sticker()->img = ImagePtr(_data, _loader->imageFormat(), _loader->imagePixmap()); + } + destroyLoaderDelayed(); + } + notifyLayoutChanged(); + } + return !data().isEmpty() || !filepath(type).isEmpty(); +} + +void DocumentData::destroyLoaderDelayed(mtpFileLoader *newValue) const { + _loader->stop(); + auto loader = std::unique_ptr(std::exchange(_loader, newValue)); + Auth().downloader().delayedDestroyLoader(std::move(loader)); +} + +bool DocumentData::loading() const { + return _loader && _loader != CancelledMtpFileLoader; +} + +QString DocumentData::loadingFilePath() const { + return loading() ? _loader->fileName() : QString(); +} + +bool DocumentData::displayLoading() const { + return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading(); +} + +float64 DocumentData::progress() const { + if (uploading()) { + return snap((size > 0) ? float64(uploadOffset) / size : 0., 0., 1.); + } + return loading() ? _loader->currentProgress() : (loaded() ? 1. : 0.); +} + +int32 DocumentData::loadOffset() const { + return loading() ? _loader->currentOffset() : 0; +} + +bool DocumentData::uploading() const { + return status == FileUploading; +} + +void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) { + if (loaded(FilePathResolveChecked)) { + auto &l = location(true); + if (!toFile.isEmpty()) { + if (!_data.isEmpty()) { + QFile f(toFile); + f.open(QIODevice::WriteOnly); + f.write(_data); + f.close(); + + setLocation(FileLocation(toFile)); + Local::writeFileLocation(mediaKey(), FileLocation(toFile)); + } else if (l.accessEnable()) { + auto alreadyName = l.name(); + if (alreadyName != toFile) { + QFile(toFile).remove(); + QFile(alreadyName).copy(toFile); + } + l.accessDisable(); + } + } + _actionOnLoad = action; + _actionOnLoadMsgId = actionMsgId; + performActionOnLoad(); + return; + } + + if (_loader == CancelledMtpFileLoader) _loader = nullptr; + if (_loader) { + if (!_loader->setFileName(toFile)) { + cancel(); // changes _actionOnLoad + _loader = nullptr; + } + } + + _actionOnLoad = action; + _actionOnLoadMsgId = actionMsgId; + if (_loader) { + if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); + } else { + status = FileReady; + if (!_access && !_url.isEmpty()) { + _loader = new webFileLoader(_url, toFile, fromCloud, autoLoading); + } else { + _loader = new mtpFileLoader(_dc, id, _access, _version, locationType(), toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading); + } + _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(documentLoadProgress(FileLoader*))); + _loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(documentLoadFailed(FileLoader*,bool))); + _loader->start(); + } + notifyLayoutChanged(); +} + +void DocumentData::cancel() { + if (!loading()) return; + + auto loader = std::unique_ptr(std::exchange(_loader, CancelledMtpFileLoader)); + loader->cancel(); + loader->stop(); + Auth().downloader().delayedDestroyLoader(std::move(loader)); + + notifyLayoutChanged(); + if (auto main = App::main()) { + main->documentLoadProgress(this); + } + + _actionOnLoad = ActionOnLoadNone; +} + +void DocumentData::notifyLayoutChanged() const { + auto &items = App::documentItems(); + for (auto item : items.value(const_cast(this))) { + Notify::historyItemLayoutChanged(item); + } + + if (auto items = InlineBots::Layout::documentItems()) { + for (auto item : items->value(const_cast(this))) { + item->layoutChanged(); + } + } +} + +VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) { + auto bitsCount = static_cast(encoded5bit.size() * 8); + auto valuesCount = bitsCount / 5; + if (!valuesCount) { + return VoiceWaveform(); + } + + // Read each 5 bit of encoded5bit as 0-31 unsigned char. + // We count the index of the byte in which the desired 5-bit sequence starts. + // And then we read a uint16 starting from that byte to guarantee to get all of those 5 bits. + // + // BUT! if it is the last byte we have, we're not allowed to read a uint16 starting with it. + // Because it will be an overflow (we'll access one byte after the available memory). + // We see, that only the last 5 bits could start in the last available byte and be problematic. + // So we read in a general way all the entries in a general way except the last one. + auto result = VoiceWaveform(valuesCount, 0); + auto bitsData = encoded5bit.constData(); + for (auto i = 0, l = valuesCount - 1; i != l; ++i) { + auto byteIndex = (i * 5) / 8; + auto bitShift = (i * 5) % 8; + auto value = *reinterpret_cast(bitsData + byteIndex); + result[i] = static_cast((value >> bitShift) & 0x1F); + } + auto lastByteIndex = ((valuesCount - 1) * 5) / 8; + auto lastBitShift = ((valuesCount - 1) * 5) % 8; + auto lastValue = (lastByteIndex == encoded5bit.size() - 1) + ? static_cast(*reinterpret_cast(bitsData + lastByteIndex)) + : *reinterpret_cast(bitsData + lastByteIndex); + result[valuesCount - 1] = static_cast((lastValue >> lastBitShift) & 0x1F); + + return result; +} + +QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform) { + auto bitsCount = waveform.size() * 5; + auto bytesCount = (bitsCount + 7) / 8; + auto result = QByteArray(bytesCount + 1, 0); + auto bitsData = result.data(); + + // Write each 0-31 unsigned char as 5 bit to result. + // We reserve one extra byte to be able to dereference any of required bytes + // as a uint16 without overflowing, even the byte with index "bytesCount - 1". + for (auto i = 0, l = waveform.size(); i < l; ++i) { + auto byteIndex = (i * 5) / 8; + auto bitShift = (i * 5) % 8; + auto value = (static_cast(waveform[i]) & 0x1F) << bitShift; + *reinterpret_cast(bitsData + byteIndex) |= value; + } + result.resize(bytesCount); + return result; +} + +QByteArray DocumentData::data() const { + return _data; +} + +const FileLocation &DocumentData::location(bool check) const { + if (check && !_location.check()) { + const_cast(this)->_location = Local::readFileLocation(mediaKey()); + } + return _location; +} + +void DocumentData::setLocation(const FileLocation &loc) { + if (loc.check()) { + _location = loc; + } +} + +QString DocumentData::filepath(FilePathResolveType type, bool forceSavingAs) const { + bool check = (type != FilePathResolveCached); + QString result = (check && _location.name().isEmpty()) ? QString() : location(check).name(); + bool saveFromData = result.isEmpty() && !data().isEmpty(); + if (saveFromData) { + if (type != FilePathResolveSaveFromData && type != FilePathResolveSaveFromDataSilent) { + saveFromData = false; + } else if (type == FilePathResolveSaveFromDataSilent && (Global::AskDownloadPath() || forceSavingAs)) { + saveFromData = false; + } + } + if (saveFromData) { + QString filename = documentSaveFilename(this, forceSavingAs); + if (!filename.isEmpty()) { + QFile f(filename); + if (f.open(QIODevice::WriteOnly)) { + if (f.write(data()) == data().size()) { + f.close(); + const_cast(this)->_location = FileLocation(filename); + Local::writeFileLocation(mediaKey(), _location); + result = filename; + } + } + } + } + return result; +} + +ImagePtr DocumentData::makeReplyPreview() { + if (replyPreview->isNull() && !thumb->isNull()) { + if (thumb->loaded()) { + int w = thumb->width(), h = thumb->height(); + if (w <= 0) w = 1; + if (h <= 0) h = 1; + auto thumbSize = (w > h) ? QSize(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : QSize(st::msgReplyBarSize.height(), h * st::msgReplyBarSize.height() / w); + thumbSize *= cIntRetinaFactor(); + auto options = Images::Option::Smooth | (isRoundVideo() ? Images::Option::Circled : Images::Option::None) | Images::Option::TransparentBackground; + auto outerSize = st::msgReplyBarSize.height(); + auto image = thumb->pixNoCache(thumbSize.width(), thumbSize.height(), options, outerSize, outerSize); + replyPreview = ImagePtr(image, "PNG"); + } else { + thumb->load(); + } + } + return replyPreview; +} + +bool fileIsImage(const QString &name, const QString &mime) { + QString lowermime = mime.toLower(), namelower = name.toLower(); + if (lowermime.startsWith(qstr("image/"))) { + return true; + } else if (namelower.endsWith(qstr(".bmp")) + || namelower.endsWith(qstr(".jpg")) + || namelower.endsWith(qstr(".jpeg")) + || namelower.endsWith(qstr(".gif")) + || namelower.endsWith(qstr(".webp")) + || namelower.endsWith(qstr(".tga")) + || namelower.endsWith(qstr(".tiff")) + || namelower.endsWith(qstr(".tif")) + || namelower.endsWith(qstr(".psd")) + || namelower.endsWith(qstr(".png"))) { + return true; + } + return false; +} + +void DocumentData::recountIsImage() { + if (isAnimation() || isVideo()) { + return; + } + _duration = fileIsImage(name, mime) ? 1 : -1; // hack +} + +bool DocumentData::setRemoteVersion(int32 version) { + if (_version == version) { + return false; + } + _version = version; + _location = FileLocation(); + _data = QByteArray(); + status = FileReady; + if (loading()) { + destroyLoaderDelayed(); + } + return true; +} + +void DocumentData::setRemoteLocation(int32 dc, uint64 access) { + _dc = dc; + _access = access; + if (isValid()) { + if (_location.check()) { + Local::writeFileLocation(mediaKey(), _location); + } else { + _location = Local::readFileLocation(mediaKey()); + } + } +} + +void DocumentData::setContentUrl(const QString &url) { + _url = url; +} + +void DocumentData::collectLocalData(DocumentData *local) { + if (local == this) return; + + if (!local->_data.isEmpty()) { + _data = local->_data; + if (voice()) { + if (!Local::copyAudio(local->mediaKey(), mediaKey())) { + Local::writeAudio(mediaKey(), _data); + } + } else { + if (!Local::copyStickerImage(local->mediaKey(), mediaKey())) { + Local::writeStickerImage(mediaKey(), _data); + } + } + } + if (!local->_location.isEmpty()) { + _location = local->_location; + Local::writeFileLocation(mediaKey(), _location); + } +} + +DocumentData::~DocumentData() { + if (loading()) { + destroyLoaderDelayed(); + } +} + +QString DocumentData::composeNameString(const QString &filename, const QString &songTitle, const QString &songPerformer) { + if (songTitle.isEmpty() && songPerformer.isEmpty()) { + return filename.isEmpty() ? qsl("Unknown File") : filename; + } + + if (songPerformer.isEmpty()) { + return songTitle; + } + + auto trackTitle = (songTitle.isEmpty() ? qsl("Unknown Track") : songTitle); + return songPerformer + QString::fromUtf8(" \xe2\x80\x93 ") + trackTitle; +} diff --git a/Telegram/SourceFiles/data/data_document.h b/Telegram/SourceFiles/data/data_document.h new file mode 100644 index 000000000..8c97a1988 --- /dev/null +++ b/Telegram/SourceFiles/data/data_document.h @@ -0,0 +1,401 @@ +/* +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 "data/data_types.h" + +inline uint64 mediaMix32To64(int32 a, int32 b) { + return (uint64(*reinterpret_cast(&a)) << 32) + | uint64(*reinterpret_cast(&b)); +} + +// Old method, should not be used anymore. +//inline MediaKey mediaKey(LocationType type, int32 dc, const uint64 &id) { +// return MediaKey(mediaMix32To64(type, dc), id); +//} +// New method when version was introduced, type is not relevant anymore (all files are Documents). +inline MediaKey mediaKey( + LocationType type, + int32 dc, + const uint64 &id, + int32 version) { + return (version > 0) ? MediaKey(mediaMix32To64(version, dc), id) : MediaKey(mediaMix32To64(type, dc), id); +} + +inline StorageKey mediaKey(const MTPDfileLocation &location) { + return storageKey( + location.vdc_id.v, + location.vvolume_id.v, + location.vlocal_id.v); +} + +struct DocumentAdditionalData { + virtual ~DocumentAdditionalData() = default; + +}; + +struct StickerData : public DocumentAdditionalData { + ImagePtr img; + QString alt; + + MTPInputStickerSet set = MTP_inputStickerSetEmpty(); + bool setInstalled() const; + + StorageImageLocation loc; // doc thumb location + +}; + +struct SongData : public DocumentAdditionalData { + int32 duration = 0; + QString title, performer; + +}; + +struct VoiceData : public DocumentAdditionalData { + ~VoiceData(); + + int duration = 0; + VoiceWaveform waveform; + char wavemax = 0; +}; + +bool fileIsImage(const QString &name, const QString &mime); + +namespace Serialize { +class Document; +} // namespace Serialize; + +class DocumentData { +public: + static DocumentData *create(DocumentId id); + static DocumentData *create( + DocumentId id, + int32 dc, + uint64 accessHash, + int32 version, + const QVector &attributes); + static DocumentData *create( + DocumentId id, + const QString &url, + const QVector &attributes); + + void setattributes( + const QVector &attributes); + + void automaticLoad(const HistoryItem *item); // auto load sticker or video + void automaticLoadSettingsChanged(); + + enum FilePathResolveType { + FilePathResolveCached, + FilePathResolveChecked, + FilePathResolveSaveFromData, + FilePathResolveSaveFromDataSilent, + }; + bool loaded( + FilePathResolveType type = FilePathResolveCached) const; + bool loading() const; + QString loadingFilePath() const; + bool displayLoading() const; + void save( + const QString &toFile, + ActionOnLoad action = ActionOnLoadNone, + const FullMsgId &actionMsgId = FullMsgId(), + LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal, + bool autoLoading = false); + void cancel(); + float64 progress() const; + int32 loadOffset() const; + bool uploading() const; + + QByteArray data() const; + const FileLocation &location(bool check = false) const; + void setLocation(const FileLocation &loc); + + QString filepath( + FilePathResolveType type = FilePathResolveCached, + bool forceSavingAs = false) const; + + bool saveToCache() const; + + void performActionOnLoad(); + + void forget(); + ImagePtr makeReplyPreview(); + + StickerData *sticker() { + return (type == StickerDocument) + ? static_cast(_additional.get()) + : nullptr; + } + void checkSticker() { + StickerData *s = sticker(); + if (!s) return; + + automaticLoad(nullptr); + if (s->img->isNull() && loaded()) { + if (_data.isEmpty()) { + const FileLocation &loc(location(true)); + if (loc.accessEnable()) { + s->img = ImagePtr(loc.name()); + loc.accessDisable(); + } + } else { + s->img = ImagePtr(_data); + } + } + } + SongData *song() { + return (type == SongDocument) + ? static_cast(_additional.get()) + : nullptr; + } + const SongData *song() const { + return const_cast(this)->song(); + } + VoiceData *voice() { + return (type == VoiceDocument) + ? static_cast(_additional.get()) + : nullptr; + } + const VoiceData *voice() const { + return const_cast(this)->voice(); + } + bool isRoundVideo() const { + return (type == RoundVideoDocument); + } + bool isAnimation() const { + return (type == AnimatedDocument) + || isRoundVideo() + || !mime.compare(qstr("image/gif"), Qt::CaseInsensitive); + } + bool isGifv() const { + return (type == AnimatedDocument) + && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive); + } + bool isTheme() const { + return + name.endsWith( + qstr(".tdesktop-theme"), + Qt::CaseInsensitive) + || name.endsWith( + qstr(".tdesktop-palette"), + Qt::CaseInsensitive); + } + bool tryPlaySong() const { + return (song() != nullptr) + || mime.startsWith(qstr("audio/"), Qt::CaseInsensitive); + } + bool isMusic() const { + if (auto s = song()) { + return (s->duration > 0); + } + return false; + } + bool isVideo() const { + return (type == VideoDocument); + } + int32 duration() const { + return (isAnimation() || isVideo()) ? _duration : -1; + } + bool isImage() const { + return !isAnimation() && !isVideo() && (_duration > 0); + } + void recountIsImage(); + void setData(const QByteArray &data) { + _data = data; + } + + bool setRemoteVersion(int32 version); // Returns true if version has changed. + void setRemoteLocation(int32 dc, uint64 access); + void setContentUrl(const QString &url); + bool hasRemoteLocation() const { + return (_dc != 0 && _access != 0); + } + bool isValid() const { + return hasRemoteLocation() || !_url.isEmpty(); + } + MTPInputDocument mtpInput() const { + if (_access) { + return MTP_inputDocument( + MTP_long(id), + MTP_long(_access)); + } + return MTP_inputDocumentEmpty(); + } + + // When we have some client-side generated document + // (for example for displaying an external inline bot result) + // and it has downloaded data, we can collect that data from it + // to (this) received from the server "same" document. + void collectLocalData(DocumentData *local); + + ~DocumentData(); + + DocumentId id = 0; + DocumentType type = FileDocument; + QSize dimensions; + int32 date = 0; + QString name; + QString mime; + ImagePtr thumb, replyPreview; + int32 size = 0; + + FileStatus status = FileReady; + int32 uploadOffset = 0; + + int32 md5[8]; + + MediaKey mediaKey() const { + return ::mediaKey(locationType(), _dc, id, _version); + } + + static QString composeNameString( + const QString &filename, + const QString &songTitle, + const QString &songPerformer); + QString composeNameString() const { + if (auto songData = song()) { + return composeNameString( + name, + songData->title, + songData->performer); + } + return composeNameString(name, QString(), QString()); + } + +private: + DocumentData( + DocumentId id, + int32 dc, + uint64 accessHash, + int32 version, + const QString &url, + const QVector &attributes); + + friend class Serialize::Document; + + LocationType locationType() const { + return voice() + ? AudioFileLocation + : isVideo() + ? VideoFileLocation + : DocumentFileLocation; + } + + // Two types of location: from MTProto by dc+access+version or from web by url + int32 _dc = 0; + uint64 _access = 0; + int32 _version = 0; + QString _url; + + FileLocation _location; + QByteArray _data; + std::unique_ptr _additional; + int32 _duration = -1; + + ActionOnLoad _actionOnLoad = ActionOnLoadNone; + FullMsgId _actionOnLoadMsgId; + mutable FileLoader *_loader = nullptr; + + void notifyLayoutChanged() const; + + void destroyLoaderDelayed( + mtpFileLoader *newValue = nullptr) const; + +}; + +VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit); +QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform); + +class DocumentClickHandler : public LeftButtonClickHandler { +public: + DocumentClickHandler(DocumentData *document) + : _document(document) { + } + DocumentData *document() const { + return _document; + } + +private: + DocumentData *_document; + +}; + +class DocumentSaveClickHandler : public DocumentClickHandler { +public: + using DocumentClickHandler::DocumentClickHandler; + static void doSave( + DocumentData *document, + bool forceSavingAs = false); + +protected: + void onClickImpl() const override; + +}; + +class DocumentOpenClickHandler : public DocumentClickHandler { +public: + using DocumentClickHandler::DocumentClickHandler; + static void doOpen( + DocumentData *document, + HistoryItem *context, + ActionOnLoad action = ActionOnLoadOpen); + +protected: + void onClickImpl() const override; + +}; + +class DocumentCancelClickHandler : public DocumentClickHandler { +public: + using DocumentClickHandler::DocumentClickHandler; + +protected: + void onClickImpl() const override; + +}; + +class GifOpenClickHandler : public DocumentOpenClickHandler { +public: + using DocumentOpenClickHandler::DocumentOpenClickHandler; + +protected: + void onClickImpl() const override; + +}; + +class VoiceSeekClickHandler : public DocumentOpenClickHandler { +public: + using DocumentOpenClickHandler::DocumentOpenClickHandler; + +protected: + void onClickImpl() const override { + } + +}; + +QString saveFileName( + const QString &title, + const QString &filter, + const QString &prefix, + QString name, + bool savingAs, + const QDir &dir = QDir()); diff --git a/Telegram/SourceFiles/data/data_flags.h b/Telegram/SourceFiles/data/data_flags.h new file mode 100644 index 000000000..85452821c --- /dev/null +++ b/Telegram/SourceFiles/data/data_flags.h @@ -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 +*/ +#pragma once + +#include + +namespace Data { + +template < + typename FlagsType, + typename FlagsType::Type kEssential = -1> +class Flags { +public: + struct Change { + Change(FlagsType diff, FlagsType value) + : diff(diff) + , value(value) { + } + FlagsType diff = 0; + FlagsType value = 0; + }; + + Flags() = default; + Flags(FlagsType value) : _value(value) { + } + + void set(FlagsType which) { + if (auto diff = which ^ _value) { + _value = which; + updated(diff); + } + } + void add(FlagsType which) { + if (auto diff = which & ~_value) { + _value |= which; + updated(diff); + } + } + void remove(FlagsType which) { + if (auto diff = which & _value) { + _value &= ~which; + updated(diff); + } + } + FlagsType current() const { + return _value; + } + rpl::producer changes() const { + return _changes.events(); + } + rpl::producer value() const { + return _changes.events_starting_with({ + FlagsType::from_raw(kEssential), + _value }); + } + +private: + void updated(FlagsType diff) { + if ((diff &= FlagsType::from_raw(kEssential))) { + _changes.fire({ diff, _value }); + } + } + + FlagsType _value = 0; + rpl::event_stream _changes; + +}; + +} // namespace Data diff --git a/Telegram/SourceFiles/data/data_game.h b/Telegram/SourceFiles/data/data_game.h new file mode 100644 index 000000000..3c19459ba --- /dev/null +++ b/Telegram/SourceFiles/data/data_game.h @@ -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 "data/data_photo.h" +#include "data/data_document.h" + +struct GameData { + GameData(const GameId &id) : id(id) { + } + GameData( + const GameId &id, + const uint64 &accessHash, + const QString &shortName, + const QString &title, + const QString &description, + PhotoData *photo, + DocumentData *document) + : id(id) + , accessHash(accessHash) + , shortName(shortName) + , title(title) + , description(description) + , photo(photo) + , document(document) { + } + + void forget() { + if (document) document->forget(); + if (photo) photo->forget(); + } + + GameId id = 0; + uint64 accessHash = 0; + QString shortName; + QString title; + QString description; + PhotoData *photo = nullptr; + DocumentData *document = nullptr; + +}; diff --git a/Telegram/SourceFiles/data/data_peer.cpp b/Telegram/SourceFiles/data/data_peer.cpp new file mode 100644 index 000000000..096143157 --- /dev/null +++ b/Telegram/SourceFiles/data/data_peer.cpp @@ -0,0 +1,1149 @@ +/* +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 "data/data_peer.h" + +#include +#include +#include "lang/lang_keys.h" +#include "observer_peer.h" +#include "mainwidget.h" +#include "apiwrap.h" +#include "boxes/confirm_box.h" +#include "styles/style_history.h" +#include "auth_session.h" +#include "messenger.h" + +namespace { + +constexpr auto kUpdateFullPeerTimeout = TimeMs(5000); // Not more than once in 5 seconds. + +int peerColorIndex(const PeerId &peer) { + auto myId = Auth().userId(); + auto peerId = peerToBareInt(peer); + auto both = (QByteArray::number(peerId) + QByteArray::number(myId)).mid(0, 15); + uchar md5[16]; + hashMd5(both.constData(), both.size(), md5); + return (md5[peerId & 0x0F] & (peerIsUser(peer) ? 0x07 : 0x03)); +} + +ImagePtr generateUserpicImage(const style::icon &icon) { + auto data = QImage(icon.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + data.setDevicePixelRatio(cRetinaFactor()); + { + Painter p(&data); + icon.paint(p, 0, 0, icon.width()); + } + return ImagePtr(App::pixmapFromImageInPlace(std::move(data)), "PNG"); +} + +} // namespace + +style::color peerUserpicColor(int index) { + static style::color peerColors[kUserColorsCount] = { + st::historyPeer1UserpicBg, + st::historyPeer2UserpicBg, + st::historyPeer3UserpicBg, + st::historyPeer4UserpicBg, + st::historyPeer5UserpicBg, + st::historyPeer6UserpicBg, + st::historyPeer7UserpicBg, + st::historyPeer8UserpicBg, + }; + return peerColors[index]; +} + +class EmptyUserpic::Impl { +public: + Impl(int index, const QString &name) : _color(peerUserpicColor(index)) { + fillString(name); + } + + void paint(Painter &p, int x, int y, int size); + void paintRounded(Painter &p, int x, int y, int size); + void paintSquare(Painter &p, int x, int y, int size); + StorageKey uniqueKey() const; + +private: + template + void paint(Painter &p, int x, int y, int size, PaintBackground paintBackground); + + void fillString(const QString &name); + + style::color _color; + QString _string; + +}; + +template +void EmptyUserpic::Impl::paint(Painter &p, int x, int y, int size, PaintBackground paintBackground) { + auto fontsize = (size * 13) / 33; + auto font = st::historyPeerUserpicFont->f; + font.setPixelSize(fontsize); + + PainterHighQualityEnabler hq(p); + p.setBrush(_color); + p.setPen(Qt::NoPen); + paintBackground(); + + p.setFont(font); + p.setBrush(Qt::NoBrush); + p.setPen(st::historyPeerUserpicFg); + p.drawText(QRect(x, y, size, size), _string, QTextOption(style::al_center)); +} + +void EmptyUserpic::Impl::paint(Painter &p, int x, int y, int size) { + paint(p, x, y, size, [&p, x, y, size] { + p.drawEllipse(x, y, size, size); + }); +} + +void EmptyUserpic::Impl::paintRounded(Painter &p, int x, int y, int size) { + paint(p, x, y, size, [&p, x, y, size] { + p.drawRoundedRect(x, y, size, size, st::buttonRadius, st::buttonRadius); + }); +} + +void EmptyUserpic::Impl::paintSquare(Painter &p, int x, int y, int size) { + paint(p, x, y, size, [&p, x, y, size] { + p.fillRect(x, y, size, size, p.brush()); + }); +} + +StorageKey EmptyUserpic::Impl::uniqueKey() const { + auto first = 0xFFFFFFFF00000000ULL | anim::getPremultiplied(_color->c); + auto second = uint64(0); + memcpy(&second, _string.constData(), qMin(sizeof(second), _string.size() * sizeof(QChar))); + return StorageKey(first, second); +} + +void EmptyUserpic::Impl::fillString(const QString &name) { + QList letters; + QList levels; + auto level = 0; + auto letterFound = false; + auto ch = name.constData(), end = ch + name.size(); + while (ch != end) { + auto emojiLength = 0; + if (auto emoji = Ui::Emoji::Find(ch, end, &emojiLength)) { + ch += emojiLength; + } else if (ch->isHighSurrogate()) { + ++ch; + if (ch != end && ch->isLowSurrogate()) { + ++ch; + } + } else if (!letterFound && ch->isLetterOrNumber()) { + letterFound = true; + if (ch + 1 != end && chIsDiac(*(ch + 1))) { + letters.push_back(QString(ch, 2)); + levels.push_back(level); + ++ch; + } else { + letters.push_back(QString(ch, 1)); + levels.push_back(level); + } + ++ch; + } else { + if (*ch == ' ') { + level = 0; + letterFound = false; + } else if (letterFound && *ch == '-') { + level = 1; + letterFound = true; + } + ++ch; + } + } + + // We prefer the second letter to be after ' ', but it can also be after '-'. + _string = QString(); + if (!letters.isEmpty()) { + _string += letters.front(); + auto bestIndex = 0; + auto bestLevel = 2; + for (auto i = letters.size(); i != 1;) { + if (levels[--i] < bestLevel) { + bestIndex = i; + bestLevel = levels[i]; + } + } + if (bestIndex > 0) { + _string += letters[bestIndex]; + } + } + _string = _string.toUpper(); +} + +EmptyUserpic::EmptyUserpic() = default; + +EmptyUserpic::EmptyUserpic(int index, const QString &name) : _impl(std::make_unique(index, name)) { +} + +void EmptyUserpic::set(int index, const QString &name) { + _impl = std::make_unique(index, name); +} + +void EmptyUserpic::clear() { + _impl.reset(); +} + +void EmptyUserpic::paint(Painter &p, int x, int y, int outerWidth, int size) const { + Expects(_impl != nullptr); + _impl->paint(p, rtl() ? (outerWidth - x - size) : x, y, size); +} + +void EmptyUserpic::paintRounded(Painter &p, int x, int y, int outerWidth, int size) const { + Expects(_impl != nullptr); + _impl->paintRounded(p, rtl() ? (outerWidth - x - size) : x, y, size); +} + +void EmptyUserpic::paintSquare(Painter &p, int x, int y, int outerWidth, int size) const { + Expects(_impl != nullptr); + _impl->paintSquare(p, rtl() ? (outerWidth - x - size) : x, y, size); +} + +StorageKey EmptyUserpic::uniqueKey() const { + Expects(_impl != nullptr); + return _impl->uniqueKey(); +} + +QPixmap EmptyUserpic::generate(int size) { + auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + { + Painter p(&result); + paint(p, 0, 0, size, size); + } + return App::pixmapFromImageInPlace(std::move(result)); +} + +EmptyUserpic::~EmptyUserpic() = default; + +using UpdateFlag = Notify::PeerUpdate::Flag; + +NotifySettings globalNotifyAll, globalNotifyUsers, globalNotifyChats; +NotifySettingsPtr globalNotifyAllPtr = UnknownNotifySettings, globalNotifyUsersPtr = UnknownNotifySettings, globalNotifyChatsPtr = UnknownNotifySettings; + +PeerClickHandler::PeerClickHandler(not_null peer) : _peer(peer) { +} + +void PeerClickHandler::onClick(Qt::MouseButton button) const { + if (button == Qt::LeftButton && App::main()) { + if (_peer && _peer->isChannel() && App::main()->historyPeer() != _peer) { + if (!_peer->asChannel()->isPublic() && !_peer->asChannel()->amIn()) { + Ui::show(Box(lang((_peer->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); + } else { + Ui::showPeerHistory(_peer, ShowAtUnreadMsgId, Ui::ShowWay::Forward); + } + } else { + Ui::showPeerProfile(_peer); + } + } +} + +PeerData::PeerData(const PeerId &id) : id(id), _colorIndex(peerColorIndex(id)) { + nameText.setText(st::msgNameStyle, QString(), _textNameOptions); + _userpicEmpty.set(_colorIndex, QString()); +} + +void PeerData::updateNameDelayed(const QString &newName, const QString &newNameOrPhone, const QString &newUsername) { + if (name == newName) { + if (isUser()) { + if (asUser()->nameOrPhone == newNameOrPhone && asUser()->username == newUsername) { + return; + } + } else if (isChannel()) { + if (asChannel()->username == newUsername) { + return; + } + } else if (isChat()) { + return; + } + } + + ++nameVersion; + name = newName; + nameText.setText(st::msgNameStyle, name, _textNameOptions); + if (!_userpic) { + _userpicEmpty.set(_colorIndex, name); + } + + Notify::PeerUpdate update(this); + update.flags |= UpdateFlag::NameChanged; + update.oldNames = names; + update.oldNameFirstChars = chars; + + if (isUser()) { + if (asUser()->username != newUsername) { + asUser()->username = newUsername; + update.flags |= UpdateFlag::UsernameChanged; + } + asUser()->setNameOrPhone(newNameOrPhone); + } else if (isChannel()) { + if (asChannel()->username != newUsername) { + asChannel()->username = newUsername; + if (newUsername.isEmpty()) { + asChannel()->removeFlags( + MTPDchannel::Flag::f_username); + } else { + asChannel()->addFlags(MTPDchannel::Flag::f_username); + } + update.flags |= UpdateFlag::UsernameChanged; + } + } + fillNames(); + Notify::PeerUpdated().notify(update, true); +} + +ClickHandlerPtr PeerData::createOpenLink() { + return MakeShared(this); +} + +void PeerData::setUserpic(ImagePtr userpic) { + _userpic = userpic; + if (!_userpic || !_userpic->loaded()) { + _userpicEmpty.set(_colorIndex, name); + } else { + _userpicEmpty.clear(); + } +} + +ImagePtr PeerData::currentUserpic() const { + if (_userpic) { + _userpic->load(); + if (_userpic->loaded()) { + _userpicEmpty.clear(); + return _userpic; + } + } + return ImagePtr(); +} + +void PeerData::paintUserpic(Painter &p, int x, int y, int size) const { + if (auto userpic = currentUserpic()) { + p.drawPixmap(x, y, userpic->pixCircled(size, size)); + } else { + _userpicEmpty.paint(p, x, y, x + size + x, size); + } +} + +void PeerData::paintUserpicRounded(Painter &p, int x, int y, int size) const { + if (auto userpic = currentUserpic()) { + p.drawPixmap(x, y, userpic->pixRounded(size, size, ImageRoundRadius::Small)); + } else { + _userpicEmpty.paintRounded(p, x, y, x + size + x, size); + } +} + +void PeerData::paintUserpicSquare(Painter &p, int x, int y, int size) const { + if (auto userpic = currentUserpic()) { + p.drawPixmap(x, y, userpic->pix(size, size)); + } else { + _userpicEmpty.paintSquare(p, x, y, x + size + x, size); + } +} + +StorageKey PeerData::userpicUniqueKey() const { + if (photoLoc.isNull() || !_userpic || !_userpic->loaded()) { + return _userpicEmpty.uniqueKey(); + } + return storageKey(photoLoc); +} + +void PeerData::saveUserpic(const QString &path, int size) const { + genUserpic(size).save(path, "PNG"); +} + +void PeerData::saveUserpicRounded(const QString &path, int size) const { + genUserpicRounded(size).save(path, "PNG"); +} + +QPixmap PeerData::genUserpic(int size) const { + if (auto userpic = currentUserpic()) { + return userpic->pixCircled(size, size); + } + auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + { + Painter p(&result); + paintUserpic(p, 0, 0, size); + } + return App::pixmapFromImageInPlace(std::move(result)); +} + +QPixmap PeerData::genUserpicRounded(int size) const { + if (auto userpic = currentUserpic()) { + return userpic->pixRounded(size, size, ImageRoundRadius::Small); + } + auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); + result.setDevicePixelRatio(cRetinaFactor()); + result.fill(Qt::transparent); + { + Painter p(&result); + paintUserpicRounded(p, 0, 0, size); + } + return App::pixmapFromImageInPlace(std::move(result)); +} + +const Text &BotCommand::descriptionText() const { + if (_descriptionText.isEmpty() && !_description.isEmpty()) { + _descriptionText.setText(st::defaultTextStyle, _description, _textNameOptions); + } + return _descriptionText; +} + +bool UserData::canShareThisContact() const { + return canShareThisContactFast() || !App::phoneFromSharedContact(peerToUser(id)).isEmpty(); +} + +void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer as well + PhotoId newPhotoId = photoId; + ImagePtr newPhoto = _userpic; + StorageImageLocation newPhotoLoc = photoLoc; + switch (p.type()) { + case mtpc_userProfilePhoto: { + const auto &d(p.c_userProfilePhoto()); + newPhotoId = d.vphoto_id.v; + newPhotoLoc = App::imageLocation(160, 160, d.vphoto_small); + newPhoto = newPhotoLoc.isNull() ? ImagePtr() : ImagePtr(newPhotoLoc); + //App::feedPhoto(App::photoFromUserPhoto(peerToUser(id), MTP_int(unixtime()), p)); + } break; + default: { + newPhotoId = 0; + if (id == ServiceUserId) { + if (!_userpic) { + newPhoto = ImagePtr(App::pixmapFromImageInPlace(Messenger::Instance().logoNoMargin().scaledToWidth(160, Qt::SmoothTransformation)), "PNG"); + } + } else { + newPhoto = ImagePtr(); + } + newPhotoLoc = StorageImageLocation(); + } break; + } + if (newPhotoId != photoId || newPhoto.v() != _userpic.v() || newPhotoLoc != photoLoc) { + photoId = newPhotoId; + setUserpic(newPhoto); + photoLoc = newPhotoLoc; + Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged); + } +} + +void PeerData::fillNames() { + names.clear(); + chars.clear(); + auto toIndex = TextUtilities::RemoveAccents(name); + if (cRussianLetters().match(toIndex).hasMatch()) { + toIndex += ' ' + translitRusEng(toIndex); + } + if (isUser()) { + if (!asUser()->nameOrPhone.isEmpty() && asUser()->nameOrPhone != name) toIndex += ' ' + TextUtilities::RemoveAccents(asUser()->nameOrPhone); + if (!asUser()->username.isEmpty()) toIndex += ' ' + TextUtilities::RemoveAccents(asUser()->username); + } else if (isChannel()) { + if (!asChannel()->username.isEmpty()) toIndex += ' ' + TextUtilities::RemoveAccents(asChannel()->username); + } + toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex); + + auto namesList = TextUtilities::PrepareSearchWords(toIndex); + for (auto &name : namesList) { + names.insert(name); + chars.insert(name[0]); + } +} + +bool UserData::setAbout(const QString &newAbout) { + if (_about == newAbout) { + return false; + } + _about = newAbout; + Notify::peerUpdatedDelayed(this, UpdateFlag::AboutChanged); + return true; +} + +void UserData::setRestrictionReason(const QString &text) { + if (_restrictionReason != text) { + _restrictionReason = text; + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::RestrictionReasonChanged); + } +} + +void UserData::setCommonChatsCount(int count) { + if (_commonChatsCount != count) { + _commonChatsCount = count; + Notify::peerUpdatedDelayed(this, UpdateFlag::UserCommonChatsChanged); + } +} + +void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) { + bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty(); + + QString newFullName; + if (changeName && newFirstName.trimmed().isEmpty()) { + firstName = newLastName; + lastName = QString(); + newFullName = firstName; + } else { + if (changeName) { + firstName = newFirstName; + lastName = newLastName; + } + newFullName = lastName.isEmpty() ? firstName : lng_full_name(lt_first_name, firstName, lt_last_name, lastName); + } + updateNameDelayed(newFullName, newPhoneName, newUsername); +} + +void UserData::setPhone(const QString &newPhone) { + _phone = newPhone; +} + +void UserData::setBotInfoVersion(int version) { + if (version < 0) { + if (botInfo) { + if (!botInfo->commands.isEmpty()) { + botInfo->commands.clear(); + Notify::botCommandsChanged(this); + } + botInfo = nullptr; + Notify::userIsBotChanged(this); + } + } else if (!botInfo) { + botInfo = std::make_unique(); + botInfo->version = version; + Notify::userIsBotChanged(this); + } else if (botInfo->version < version) { + if (!botInfo->commands.isEmpty()) { + botInfo->commands.clear(); + Notify::botCommandsChanged(this); + } + botInfo->description.clear(); + botInfo->version = version; + botInfo->inited = false; + } +} + +void UserData::setBotInfo(const MTPBotInfo &info) { + switch (info.type()) { + case mtpc_botInfo: { + const auto &d(info.c_botInfo()); + if (peerFromUser(d.vuser_id.v) != id || !botInfo) return; + + QString desc = qs(d.vdescription); + if (botInfo->description != desc) { + botInfo->description = desc; + botInfo->text = Text(st::msgMinWidth); + } + + auto &v = d.vcommands.v; + botInfo->commands.reserve(v.size()); + auto changedCommands = false; + int32 j = 0; + for (int32 i = 0, l = v.size(); i < l; ++i) { + if (v.at(i).type() != mtpc_botCommand) continue; + + QString cmd = qs(v.at(i).c_botCommand().vcommand), desc = qs(v.at(i).c_botCommand().vdescription); + if (botInfo->commands.size() <= j) { + botInfo->commands.push_back(BotCommand(cmd, desc)); + changedCommands = true; + } else { + if (botInfo->commands[j].command != cmd) { + botInfo->commands[j].command = cmd; + changedCommands = true; + } + if (botInfo->commands[j].setDescription(desc)) { + changedCommands = true; + } + } + ++j; + } + while (j < botInfo->commands.size()) { + botInfo->commands.pop_back(); + changedCommands = true; + } + + botInfo->inited = true; + + if (changedCommands) { + Notify::botCommandsChanged(this); + } + } break; + } +} + +void UserData::setNameOrPhone(const QString &newNameOrPhone) { + if (nameOrPhone != newNameOrPhone) { + nameOrPhone = newNameOrPhone; + phoneText.setText(st::msgNameStyle, nameOrPhone, _textNameOptions); + } +} + +void UserData::madeAction(TimeId when) { + if (botInfo || isServiceUser(id) || when <= 0) return; + + if (onlineTill <= 0 && -onlineTill < when) { + onlineTill = -when - SetOnlineAfterActivity; + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::UserOnlineChanged); + } else if (onlineTill > 0 && onlineTill < when + 1) { + onlineTill = when + SetOnlineAfterActivity; + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::UserOnlineChanged); + } +} + +void UserData::setAccessHash(uint64 accessHash) { + if (accessHash == kInaccessibleAccessHashOld) { + _accessHash = 0; + _flags.add(MTPDuser_ClientFlag::f_inaccessible | 0); + } else { + _accessHash = accessHash; + } +} + +void UserData::setBlockStatus(BlockStatus blockStatus) { + if (blockStatus != _blockStatus) { + _blockStatus = blockStatus; + Notify::peerUpdatedDelayed(this, UpdateFlag::UserIsBlocked); + } +} + +void UserData::setCallsStatus(CallsStatus callsStatus) { + if (callsStatus != _callsStatus) { + _callsStatus = callsStatus; + Notify::peerUpdatedDelayed(this, UpdateFlag::UserHasCalls); + } +} + +bool UserData::hasCalls() const { + return (callsStatus() != CallsStatus::Disabled) && (callsStatus() != CallsStatus::Unknown); +} + +void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see Local::readPeer as well + PhotoId newPhotoId = photoId; + ImagePtr newPhoto = _userpic; + StorageImageLocation newPhotoLoc = photoLoc; + switch (p.type()) { + case mtpc_chatPhoto: { + const auto &d(p.c_chatPhoto()); + if (phId != UnknownPeerPhotoId) { + newPhotoId = phId; + } + newPhotoLoc = App::imageLocation(160, 160, d.vphoto_small); + newPhoto = newPhotoLoc.isNull() ? ImagePtr() : ImagePtr(newPhotoLoc); +// photoFull = newPhoto ? ImagePtr(640, 640, d.vphoto_big, ImagePtr()) : ImagePtr(); + } break; + default: { + newPhotoId = 0; + newPhotoLoc = StorageImageLocation(); + newPhoto = ImagePtr(); +// photoFull = ImagePtr(); + } break; + } + if (newPhotoId != photoId || newPhoto.v() != _userpic.v() || newPhotoLoc != photoLoc) { + photoId = newPhotoId; + setUserpic(newPhoto); + photoLoc = newPhotoLoc; + Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged); + } +} + +void ChatData::setName(const QString &newName) { + updateNameDelayed(newName.isEmpty() ? name : newName, QString(), QString()); +} + +void ChatData::invalidateParticipants() { + auto wasCanEdit = canEdit(); + participants.clear(); + admins.clear(); + removeFlags(MTPDchat::Flag::f_admin); + invitedByMe.clear(); + botStatus = 0; + if (wasCanEdit != canEdit()) { + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::ChatCanEdit); + } + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged); +} + +void ChatData::setInviteLink(const QString &newInviteLink) { + if (newInviteLink != _inviteLink) { + _inviteLink = newInviteLink; + Notify::peerUpdatedDelayed(this, UpdateFlag::InviteLinkChanged); + } +} + +ChannelData::ChannelData(const PeerId &id) +: PeerData(id) +, inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) { + flagsValue() + | rpl::filter([](const Flags::Change &change) { + return change.diff & MTPDchannel::Flag::f_megagroup; + }) + | rpl::map([](const Flags::Change &change) -> bool { + return change.value & MTPDchannel::Flag::f_megagroup; + }) + | rpl::start([this](bool megagroup) { + if (megagroup) { + if (!mgInfo) { + mgInfo = std::make_unique(); + } + } else if (mgInfo) { + mgInfo = nullptr; + } + }, _lifetime); +} + +void ChannelData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see Local::readPeer as well + PhotoId newPhotoId = photoId; + ImagePtr newPhoto = _userpic; + StorageImageLocation newPhotoLoc = photoLoc; + switch (p.type()) { + case mtpc_chatPhoto: { + const auto &d(p.c_chatPhoto()); + if (phId != UnknownPeerPhotoId) { + newPhotoId = phId; + } + newPhotoLoc = App::imageLocation(160, 160, d.vphoto_small); + newPhoto = newPhotoLoc.isNull() ? ImagePtr() : ImagePtr(newPhotoLoc); +// photoFull = newPhoto ? ImagePtr(640, 640, d.vphoto_big, newPhoto) : ImagePtr(); + } break; + default: { + newPhotoId = 0; + newPhotoLoc = StorageImageLocation(); + newPhoto = ImagePtr(); +// photoFull = ImagePtr(); + } break; + } + if (newPhotoId != photoId || newPhoto.v() != _userpic.v() || newPhotoLoc != photoLoc) { + photoId = newPhotoId; + setUserpic(newPhoto); + photoLoc = newPhotoLoc; + Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged); + } +} + +void ChannelData::setName(const QString &newName, const QString &newUsername) { + updateNameDelayed(newName.isEmpty() ? name : newName, QString(), newUsername); +} + +void PeerData::updateFull() { + if (!_lastFullUpdate || getms(true) > _lastFullUpdate + kUpdateFullPeerTimeout) { + updateFullForced(); + } +} + +void PeerData::updateFullForced() { + Auth().api().requestFullPeer(this); + if (auto channel = asChannel()) { + if (!channel->amCreator() && !channel->inviter) { + Auth().api().requestSelfParticipant(channel); + } + } +} + +void PeerData::fullUpdated() { + _lastFullUpdate = getms(true); +} + +bool ChannelData::setAbout(const QString &newAbout) { + if (_about == newAbout) { + return false; + } + _about = newAbout; + Notify::peerUpdatedDelayed(this, UpdateFlag::AboutChanged); + return true; +} + +void ChannelData::setInviteLink(const QString &newInviteLink) { + if (newInviteLink != _inviteLink) { + _inviteLink = newInviteLink; + Notify::peerUpdatedDelayed(this, UpdateFlag::InviteLinkChanged); + } +} + +void ChannelData::setMembersCount(int newMembersCount) { + if (_membersCount != newMembersCount) { + if (isMegagroup() && !mgInfo->lastParticipants.isEmpty()) { + mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; + mgInfo->lastParticipantsCount = membersCount(); + } + _membersCount = newMembersCount; + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::MembersChanged); + } +} + +void ChannelData::setAdminsCount(int newAdminsCount) { + if (_adminsCount != newAdminsCount) { + _adminsCount = newAdminsCount; + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::AdminsChanged); + } +} + +void ChannelData::setRestrictedCount(int newRestrictedCount) { + if (_restrictedCount != newRestrictedCount) { + _restrictedCount = newRestrictedCount; + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::BannedUsersChanged); + } +} + +void ChannelData::setKickedCount(int newKickedCount) { + if (_kickedCount != newKickedCount) { + _kickedCount = newKickedCount; + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::BannedUsersChanged); + } +} + +MTPChannelBannedRights ChannelData::KickedRestrictedRights() { + using Flag = MTPDchannelBannedRights::Flag; + auto flags = Flag::f_view_messages | Flag::f_send_messages | Flag::f_send_media | Flag::f_embed_links | Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_games | Flag::f_send_inline; + return MTP_channelBannedRights(MTP_flags(flags), MTP_int(std::numeric_limits::max())); +} + +void ChannelData::applyEditAdmin(not_null user, const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights) { + auto flags = Notify::PeerUpdate::Flag::AdminsChanged | Notify::PeerUpdate::Flag::None; + if (mgInfo) { + if (!mgInfo->lastParticipants.contains(user)) { // If rights are empty - still add participant? TODO check + mgInfo->lastParticipants.push_front(user); + setMembersCount(membersCount() + 1); + if (user->botInfo && !mgInfo->bots.contains(user)) { + mgInfo->bots.insert(user); + if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) { + mgInfo->botStatus = 2; + } + } + } + if (mgInfo->lastRestricted.contains(user)) { // If rights are empty - still remove restrictions? TODO check + mgInfo->lastRestricted.remove(user); + if (restrictedCount() > 0) { + setRestrictedCount(restrictedCount() - 1); + } + } + auto it = mgInfo->lastAdmins.find(user); + if (newRights.c_channelAdminRights().vflags.v != 0) { + auto lastAdmin = MegagroupInfo::Admin { newRights }; + lastAdmin.canEdit = true; + if (it == mgInfo->lastAdmins.cend()) { + mgInfo->lastAdmins.insert(user, lastAdmin); + setAdminsCount(adminsCount() + 1); + } else { + it.value() = lastAdmin; + } + } else { + if (it != mgInfo->lastAdmins.cend()) { + mgInfo->lastAdmins.erase(it); + if (adminsCount() > 0) { + setAdminsCount(adminsCount() - 1); + } + } + } + } + if (oldRights.c_channelAdminRights().vflags.v && !newRights.c_channelAdminRights().vflags.v) { + // We removed an admin. + if (adminsCount() > 1) { + setAdminsCount(adminsCount() - 1); + } + if (!isMegagroup() && user->botInfo && membersCount() > 1) { + // Removing bot admin removes it from channel. + setMembersCount(membersCount() - 1); + } + } else if (!oldRights.c_channelAdminRights().vflags.v && newRights.c_channelAdminRights().vflags.v) { + // We added an admin. + setAdminsCount(adminsCount() + 1); + updateFullForced(); + } + Notify::peerUpdatedDelayed(this, flags); +} + +void ChannelData::applyEditBanned(not_null user, const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { + auto flags = Notify::PeerUpdate::Flag::BannedUsersChanged | Notify::PeerUpdate::Flag::None; + if (mgInfo) { + if (mgInfo->lastAdmins.contains(user)) { // If rights are empty - still remove admin? TODO check + mgInfo->lastAdmins.remove(user); + if (adminsCount() > 1) { + setAdminsCount(adminsCount() - 1); + } else { + flags |= Notify::PeerUpdate::Flag::AdminsChanged; + } + } + auto isKicked = (newRights.c_channelBannedRights().vflags.v & MTPDchannelBannedRights::Flag::f_view_messages); + auto isRestricted = !isKicked && (newRights.c_channelBannedRights().vflags.v != 0); + auto it = mgInfo->lastRestricted.find(user); + if (isRestricted) { + if (it == mgInfo->lastRestricted.cend()) { + mgInfo->lastRestricted.insert(user, MegagroupInfo::Restricted { newRights }); + setRestrictedCount(restrictedCount() + 1); + } else { + it->rights = newRights; + } + } else { + if (it != mgInfo->lastRestricted.cend()) { + mgInfo->lastRestricted.erase(it); + if (restrictedCount() > 0) { + setRestrictedCount(restrictedCount() - 1); + } + } + if (isKicked) { + auto i = mgInfo->lastParticipants.indexOf(user); + if (i >= 0) { + mgInfo->lastParticipants.removeAt(i); + } + if (membersCount() > 1) { + setMembersCount(membersCount() - 1); + } else { + mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; + mgInfo->lastParticipantsCount = 0; + } + setKickedCount(kickedCount() + 1); + if (mgInfo->bots.contains(user)) { + mgInfo->bots.remove(user); + if (mgInfo->bots.isEmpty() && mgInfo->botStatus > 0) { + mgInfo->botStatus = -1; + } + } + flags |= Notify::PeerUpdate::Flag::MembersChanged; + } + } + } + Notify::peerUpdatedDelayed(this, flags); +} + +void ChannelData::setRestrictionReason(const QString &text) { + if (_restrictionReason != text) { + _restrictionReason = text; + Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::RestrictionReasonChanged); + } +} + +bool ChannelData::canNotEditLastAdmin(not_null user) const { + if (mgInfo) { + auto i = mgInfo->lastAdmins.constFind(user); + if (i != mgInfo->lastAdmins.cend()) { + return !i->canEdit; + } + return (user == mgInfo->creator); + } + return false; +} + +bool ChannelData::canEditAdmin(not_null user) const { + if (user->isSelf()) { + return false; + } else if (amCreator()) { + return true; + } else if (canNotEditLastAdmin(user)) { + return false; + } + return adminRights().is_add_admins(); +} + +bool ChannelData::canRestrictUser(not_null user) const { + if (user->isSelf()) { + return false; + } else if (amCreator()) { + return true; + } else if (canNotEditLastAdmin(user)) { + return false; + } + return adminRights().is_ban_users(); +} + +void ChannelData::setAdminRights(const MTPChannelAdminRights &rights) { + if (rights.c_channelAdminRights().vflags.v == _adminRights.c_channelAdminRights().vflags.v) { + return; + } + _adminRights = rights; + if (isMegagroup()) { + if (hasAdminRights()) { + if (!amCreator()) { + auto me = MegagroupInfo::Admin { _adminRights }; + me.canEdit = false; + mgInfo->lastAdmins.insert(App::self(), me); + } + mgInfo->lastRestricted.remove(App::self()); + } else { + mgInfo->lastAdmins.remove(App::self()); + } + } + Notify::peerUpdatedDelayed(this, UpdateFlag::ChannelRightsChanged | UpdateFlag::AdminsChanged | UpdateFlag::BannedUsersChanged); +} + +void ChannelData::setRestrictedRights(const MTPChannelBannedRights &rights) { + if (rights.c_channelBannedRights().vflags.v == _restrictedRights.c_channelBannedRights().vflags.v + && rights.c_channelBannedRights().vuntil_date.v == _restrictedRights.c_channelBannedRights().vuntil_date.v) { + return; + } + _restrictedRights = rights; + if (isMegagroup()) { + if (hasRestrictedRights()) { + if (!amCreator()) { + auto me = MegagroupInfo::Restricted { _restrictedRights }; + mgInfo->lastRestricted.insert(App::self(), me); + } + mgInfo->lastAdmins.remove(App::self()); + } else { + mgInfo->lastRestricted.remove(App::self()); + } + } + Notify::peerUpdatedDelayed(this, UpdateFlag::ChannelRightsChanged | UpdateFlag::AdminsChanged | UpdateFlag::BannedUsersChanged); +} + +uint64 PtsWaiter::ptsKey(PtsSkippedQueue queue, int32 pts) { + return _queue.insert(uint64(uint32(pts)) << 32 | (++_skippedKey), queue).key(); +} + +void PtsWaiter::setWaitingForSkipped(ChannelData *channel, int32 ms) { + if (ms >= 0) { + if (App::main()) { + App::main()->ptsWaiterStartTimerFor(channel, ms); + } + _waitingForSkipped = true; + } else { + _waitingForSkipped = false; + checkForWaiting(channel); + } +} + +void PtsWaiter::setWaitingForShortPoll(ChannelData *channel, int32 ms) { + if (ms >= 0) { + if (App::main()) { + App::main()->ptsWaiterStartTimerFor(channel, ms); + } + _waitingForShortPoll = true; + } else { + _waitingForShortPoll = false; + checkForWaiting(channel); + } +} + +void PtsWaiter::checkForWaiting(ChannelData *channel) { + if (!_waitingForSkipped && !_waitingForShortPoll && App::main()) { + App::main()->ptsWaiterStartTimerFor(channel, -1); + } +} + +void PtsWaiter::applySkippedUpdates(ChannelData *channel) { + if (!_waitingForSkipped) return; + + setWaitingForSkipped(channel, -1); + + if (_queue.isEmpty()) return; + + ++_applySkippedLevel; + for (auto i = _queue.cbegin(), e = _queue.cend(); i != e; ++i) { + switch (i.value()) { + case SkippedUpdate: Auth().api().applyUpdateNoPtsCheck(_updateQueue.value(i.key())); break; + case SkippedUpdates: Auth().api().applyUpdatesNoPtsCheck(_updatesQueue.value(i.key())); break; + } + } + --_applySkippedLevel; + clearSkippedUpdates(); +} + +void PtsWaiter::clearSkippedUpdates() { + _queue.clear(); + _updateQueue.clear(); + _updatesQueue.clear(); + _applySkippedLevel = 0; +} + +bool PtsWaiter::updated(ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates) { + if (_requesting || _applySkippedLevel) { + return true; + } else if (pts <= _good && count > 0) { + return false; + } else if (check(channel, pts, count)) { + return true; + } + _updatesQueue.insert(ptsKey(SkippedUpdates, pts), updates); + return false; +} + +bool PtsWaiter::updated(ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update) { + if (_requesting || _applySkippedLevel) { + return true; + } else if (pts <= _good && count > 0) { + return false; + } else if (check(channel, pts, count)) { + return true; + } + _updateQueue.insert(ptsKey(SkippedUpdate, pts), update); + return false; +} + +bool PtsWaiter::updated(ChannelData *channel, int32 pts, int32 count) { + if (_requesting || _applySkippedLevel) { + return true; + } else if (pts <= _good && count > 0) { + return false; + } + return check(channel, pts, count); +} + +bool PtsWaiter::updateAndApply(ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates) { + if (!updated(channel, pts, count, updates)) { + return false; + } + if (!_waitingForSkipped || _queue.isEmpty()) { + // Optimization - no need to put in queue and back. + Auth().api().applyUpdatesNoPtsCheck(updates); + } else { + _updatesQueue.insert(ptsKey(SkippedUpdates, pts), updates); + applySkippedUpdates(channel); + } + return true; +} + +bool PtsWaiter::updateAndApply(ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update) { + if (!updated(channel, pts, count, update)) { + return false; + } + if (!_waitingForSkipped || _queue.isEmpty()) { + // Optimization - no need to put in queue and back. + Auth().api().applyUpdateNoPtsCheck(update); + } else { + _updateQueue.insert(ptsKey(SkippedUpdate, pts), update); + applySkippedUpdates(channel); + } + return true; +} + +bool PtsWaiter::updateAndApply(ChannelData *channel, int32 pts, int32 count) { + if (!updated(channel, pts, count)) { + return false; + } + applySkippedUpdates(channel); + return true; +} + +bool PtsWaiter::check(ChannelData *channel, int32 pts, int32 count) { // return false if need to save that update and apply later + if (!inited()) { + init(pts); + return true; + } + + _last = qMax(_last, pts); + _count += count; + if (_last == _count) { + _good = _last; + return true; + } else if (_last < _count) { + setWaitingForSkipped(channel, 1); + } else { + setWaitingForSkipped(channel, WaitForSkippedTimeout); + } + return !count; +} diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h new file mode 100644 index 000000000..2338fab8a --- /dev/null +++ b/Telegram/SourceFiles/data/data_peer.h @@ -0,0 +1,1197 @@ +/* +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 "data/data_types.h" +#include "data/data_flags.h" + +struct NotifySettings { + NotifySettings() = default; + + bool previews() const { + return flags & MTPDpeerNotifySettings::Flag::f_show_previews; + } + bool silent() const { + return flags & MTPDpeerNotifySettings::Flag::f_silent; + } + + MTPDpeerNotifySettings::Flags flags + = MTPDpeerNotifySettings::Flag::f_show_previews; + TimeId mute = 0; + QString sound = qsl("default"); + +}; +typedef NotifySettings *NotifySettingsPtr; + +static const NotifySettingsPtr UnknownNotifySettings + = NotifySettingsPtr(0); +static const NotifySettingsPtr EmptyNotifySettings + = NotifySettingsPtr(1); +extern NotifySettings globalNotifyAll; +extern NotifySettings globalNotifyUsers; +extern NotifySettings globalNotifyChats; +extern NotifySettingsPtr globalNotifyAllPtr; +extern NotifySettingsPtr globalNotifyUsersPtr; +extern NotifySettingsPtr globalNotifyChatsPtr; + +inline bool isNotifyMuted( + NotifySettingsPtr settings, + TimeId *changeIn = nullptr) { + if (settings != UnknownNotifySettings + && settings != EmptyNotifySettings) { + auto t = unixtime(); + if (settings->mute > t) { + if (changeIn) *changeIn = settings->mute - t + 1; + return true; + } + } + if (changeIn) *changeIn = 0; + return false; +} + +static constexpr int kUserColorsCount = 8; +static constexpr int kChatColorsCount = 4; +static constexpr int kChannelColorsCount = 4; + +class EmptyUserpic { +public: + EmptyUserpic(); + EmptyUserpic(int index, const QString &name); + + void set(int index, const QString &name); + void clear(); + + explicit operator bool() const { + return (_impl != nullptr); + } + + void paint( + Painter &p, + int x, + int y, + int outerWidth, + int size) const; + void paintRounded( + Painter &p, + int x, + int y, + int outerWidth, + int size) const; + void paintSquare( + Painter &p, + int x, + int y, + int outerWidth, + int size) const; + QPixmap generate(int size); + StorageKey uniqueKey() const; + + ~EmptyUserpic(); + +private: + class Impl; + std::unique_ptr _impl; + friend class Impl; + +}; + +static const PhotoId UnknownPeerPhotoId = 0xFFFFFFFFFFFFFFFFULL; + +inline const QString &emptyUsername() { + static QString empty; + return empty; +} + +class PeerData; + +class PeerClickHandler : public ClickHandler { +public: + PeerClickHandler(not_null peer); + void onClick(Qt::MouseButton button) const override; + + not_null peer() const { + return _peer; + } + +private: + not_null _peer; + +}; + +class UserData; +class ChatData; +class ChannelData; + +class PeerData { +protected: + PeerData(const PeerId &id); + PeerData(const PeerData &other) = delete; + PeerData &operator=(const PeerData &other) = delete; + +public: + virtual ~PeerData() { + if (notify != UnknownNotifySettings + && notify != EmptyNotifySettings) { + delete base::take(notify); + } + } + + bool isUser() const { + return peerIsUser(id); + } + bool isChat() const { + return peerIsChat(id); + } + bool isChannel() const { + return peerIsChannel(id); + } + bool isSelf() const { + return (input.type() == mtpc_inputPeerSelf); + } + bool isVerified() const; + bool isMegagroup() const; + bool isMuted() const { + return (notify != EmptyNotifySettings) + && (notify != UnknownNotifySettings) + && (notify->mute >= unixtime()); + } + bool canWrite() const; + UserData *asUser(); + const UserData *asUser() const; + ChatData *asChat(); + const ChatData *asChat() const; + ChannelData *asChannel(); + const ChannelData *asChannel() const; + ChannelData *asMegagroup(); + const ChannelData *asMegagroup() const; + + ChatData *migrateFrom() const; + ChannelData *migrateTo() const; + + void updateFull(); + void updateFullForced(); + void fullUpdated(); + bool wasFullUpdated() const { + return (_lastFullUpdate != 0); + } + + const Text &dialogName() const; + const QString &shortName() const; + const QString &userName() const; + + const PeerId id; + int32 bareId() const { + return int32(uint32(id & 0xFFFFFFFFULL)); + } + + QString name; + Text nameText; + using Names = OrderedSet; + Names names; // for filtering + using NameFirstChars = OrderedSet; + NameFirstChars chars; + + enum LoadedStatus { + NotLoaded = 0x00, + MinimalLoaded = 0x01, + FullLoaded = 0x02, + }; + LoadedStatus loadedStatus = NotLoaded; + MTPinputPeer input; + + int colorIndex() const { + return _colorIndex; + } + void setUserpic(ImagePtr userpic); + void paintUserpic( + Painter &p, + int x, + int y, + int size) const; + void paintUserpicLeft( + Painter &p, + int x, + int y, + int w, + int size) const { + paintUserpic(p, rtl() ? (w - x - size) : x, y, size); + } + void paintUserpicRounded( + Painter &p, + int x, + int y, + int size) const; + void paintUserpicSquare( + Painter &p, + int x, + int y, + int size) const; + void loadUserpic(bool loadFirst = false, bool prior = true) { + _userpic->load(loadFirst, prior); + } + bool userpicLoaded() const { + return _userpic->loaded(); + } + StorageKey userpicUniqueKey() const; + void saveUserpic(const QString &path, int size) const; + void saveUserpicRounded(const QString &path, int size) const; + QPixmap genUserpic(int size) const; + QPixmap genUserpicRounded(int size) const; + + PhotoId photoId = UnknownPeerPhotoId; + StorageImageLocation photoLoc; + + int nameVersion = 1; + + NotifySettingsPtr notify = UnknownNotifySettings; + + // if this string is not empty we must not allow to open the + // conversation and we must show this string instead + virtual QString restrictionReason() const { + return QString(); + } + + ClickHandlerPtr createOpenLink(); + const ClickHandlerPtr &openLink() { + if (!_openLink) { + _openLink = createOpenLink(); + } + return _openLink; + } + + ImagePtr currentUserpic() const; + +protected: + void updateNameDelayed( + const QString &newName, + const QString &newNameOrPhone, + const QString &newUsername); + + ImagePtr _userpic; + mutable EmptyUserpic _userpicEmpty; + +private: + void fillNames(); + + ClickHandlerPtr _openLink; + + int _colorIndex = 0; + TimeMs _lastFullUpdate = 0; + +}; + +class BotCommand { +public: + BotCommand( + const QString &command, + const QString &description) + : command(command) + , _description(description) { + } + QString command; + + bool setDescription(const QString &description) { + if (_description != description) { + _description = description; + _descriptionText = Text(); + return true; + } + return false; + } + + const Text &descriptionText() const; + +private: + QString _description; + mutable Text _descriptionText; + +}; + +struct BotInfo { + bool inited = false; + bool readsAllHistory = false; + bool cantJoinGroups = false; + int version = 0; + QString description, inlinePlaceholder; + QList commands; + Text text = Text{ int(st::msgMinWidth) }; // description + + QString startToken, startGroupToken, shareGameShortName; + PeerId inlineReturnPeerId = 0; +}; + +class UserData : public PeerData { +public: + static constexpr auto kEssentialFlags = 0 + | MTPDuser::Flag::f_self + | MTPDuser::Flag::f_contact + | MTPDuser::Flag::f_mutual_contact + | MTPDuser::Flag::f_deleted + | MTPDuser::Flag::f_bot + | MTPDuser::Flag::f_bot_chat_history + | MTPDuser::Flag::f_bot_nochats + | MTPDuser::Flag::f_verified + | MTPDuser::Flag::f_restricted + | MTPDuser::Flag::f_bot_inline_geo; + using Flags = Data::Flags< + MTPDuser::Flags, + kEssentialFlags.value()>; + + static constexpr auto kEssentialFullFlags = 0 + | MTPDuserFull::Flag::f_blocked + | MTPDuserFull::Flag::f_phone_calls_available + | MTPDuserFull::Flag::f_phone_calls_private; + using FullFlags = Data::Flags< + MTPDuserFull::Flags, + kEssentialFullFlags.value()>; + + UserData(const PeerId &id) : PeerData(id) { + } + void setPhoto(const MTPUserProfilePhoto &photo); + + void setName( + const QString &newFirstName, + const QString &newLastName, + const QString &newPhoneName, + const QString &newUsername); + + void setPhone(const QString &newPhone); + void setBotInfoVersion(int version); + void setBotInfo(const MTPBotInfo &info); + + void setNameOrPhone(const QString &newNameOrPhone); + + void madeAction(TimeId when); // pseudo-online + + uint64 accessHash() const { + return _accessHash; + } + void setAccessHash(uint64 accessHash); + + void setFlags(MTPDuser::Flags which) { + _flags.set(which); + } + void addFlags(MTPDuser::Flags which) { + _flags.add(which); + } + void removeFlags(MTPDuser::Flags which) { + _flags.remove(which); + } + MTPDuser::Flags flags() const { + return _flags.current(); + } + rpl::producer flagsValue() const { + return _flags.value(); + } + + void setFullFlags(MTPDuserFull::Flags which) { + _fullFlags.set(which); + } + void addFullFlags(MTPDuserFull::Flags which) { + _fullFlags.add(which); + } + void removeFullFlags(MTPDuserFull::Flags which) { + _fullFlags.remove(which); + } + MTPDuserFull::Flags fullFlags() const { + return _fullFlags.current(); + } + rpl::producer fullFlagsValue() const { + return _fullFlags.value(); + } + + bool isVerified() const { + return flags() & MTPDuser::Flag::f_verified; + } + bool isBotInlineGeo() const { + return flags() & MTPDuser::Flag::f_bot_inline_geo; + } + bool isInaccessible() const { + return flags() & MTPDuser_ClientFlag::f_inaccessible; + } + bool canWrite() const { + return !isInaccessible(); + } + bool isContact() const { + return (contact > 0); + } + + bool canShareThisContact() const; + bool canAddContact() const { + return canShareThisContact() && !isContact(); + } + + // In feedUsers() we check only that. + // When actually trying to share contact we perform + // a full check by canShareThisContact() call. + bool canShareThisContactFast() const { + return !_phone.isEmpty(); + } + + MTPInputUser inputUser; + + QString firstName; + QString lastName; + QString username; + const QString &phone() const { + return _phone; + } + QString nameOrPhone; + Text phoneText; + TimeId onlineTill = 0; + int32 contact = -1; // -1 - not contact, cant add (self, empty, deleted, foreign), 0 - not contact, can add (request), 1 - contact + + enum class BlockStatus { + Unknown, + Blocked, + NotBlocked, + }; + BlockStatus blockStatus() const { + return _blockStatus; + } + bool isBlocked() const { + return (blockStatus() == BlockStatus::Blocked); + } + void setBlockStatus(BlockStatus blockStatus); + + enum class CallsStatus { + Unknown, + Enabled, + Disabled, + Private, + }; + CallsStatus callsStatus() const { + return _callsStatus; + } + bool hasCalls() const; + void setCallsStatus(CallsStatus callsStatus); + + bool setAbout(const QString &newAbout); + const QString &about() const { + return _about; + } + + std::unique_ptr botInfo; + + QString restrictionReason() const override { + return _restrictionReason; + } + void setRestrictionReason(const QString &reason); + + int commonChatsCount() const { + return _commonChatsCount; + } + void setCommonChatsCount(int count); + +private: + Flags _flags; + FullFlags _fullFlags; + + QString _restrictionReason; + QString _about; + QString _phone; + BlockStatus _blockStatus = BlockStatus::Unknown; + CallsStatus _callsStatus = CallsStatus::Unknown; + int _commonChatsCount = 0; + + uint64 _accessHash = 0; + static constexpr auto kInaccessibleAccessHashOld + = 0xFFFFFFFFFFFFFFFFULL; + +}; + +class ChatData : public PeerData { +public: + static constexpr auto kEssentialFlags = 0 + | MTPDchat::Flag::f_creator + | MTPDchat::Flag::f_kicked + | MTPDchat::Flag::f_left + | MTPDchat::Flag::f_admins_enabled + | MTPDchat::Flag::f_admin + | MTPDchat::Flag::f_deactivated + | MTPDchat::Flag::f_migrated_to; + using Flags = Data::Flags< + MTPDchat::Flags, + kEssentialFlags>; + + ChatData(const PeerId &id) + : PeerData(id) + , inputChat(MTP_int(bareId())) { + } + void setPhoto( + const MTPChatPhoto &photo, + const PhotoId &phId = UnknownPeerPhotoId); + + void setName(const QString &newName); + + void invalidateParticipants(); + bool noParticipantInfo() const { + return (count > 0 || amIn()) && participants.isEmpty(); + } + + MTPint inputChat; + + ChannelData *migrateToPtr = nullptr; + + int count = 0; + TimeId date = 0; + int version = 0; + UserId creator = 0; + + void setFlags(MTPDchat::Flags which) { + _flags.set(which); + } + void addFlags(MTPDchat::Flags which) { + _flags.add(which); + } + void removeFlags(MTPDchat::Flags which) { + _flags.remove(which); + } + MTPDchat::Flags flags() const { + return _flags.current(); + } + rpl::producer flagsValue() const { + return _flags.value(); + } + + bool isForbidden() const { + return flags() & MTPDchat_ClientFlag::f_forbidden; + } + bool amIn() const { + return !isForbidden() && !haveLeft() && !wasKicked(); + } + bool canEdit() const { + return !isDeactivated() + && (amCreator() + || (adminsEnabled() ? amAdmin() : amIn())); + } + bool canWrite() const { + return !isDeactivated() && amIn(); + } + bool haveLeft() const { + return flags() & MTPDchat::Flag::f_left; + } + bool wasKicked() const { + return flags() & MTPDchat::Flag::f_kicked; + } + bool adminsEnabled() const { + return flags() & MTPDchat::Flag::f_admins_enabled; + } + bool amCreator() const { + return flags() & MTPDchat::Flag::f_creator; + } + bool amAdmin() const { + return (flags() & MTPDchat::Flag::f_admin) && adminsEnabled(); + } + bool isDeactivated() const { + return flags() & MTPDchat::Flag::f_deactivated; + } + bool isMigrated() const { + return flags() & MTPDchat::Flag::f_migrated_to; + } + QMap, int> participants; + OrderedSet> invitedByMe; + OrderedSet> admins; + QList> lastAuthors; + OrderedSet> markupSenders; + int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other +// ImagePtr photoFull; + + void setInviteLink(const QString &newInviteLink); + QString inviteLink() const { + return _inviteLink; + } + +private: + void flagsUpdated(MTPDchat::Flags diff); + + Flags _flags; + QString _inviteLink; + +}; + +enum PtsSkippedQueue { + SkippedUpdate, + SkippedUpdates, +}; +class PtsWaiter { +public: + PtsWaiter() = default; + void init(int32 pts) { + _good = _last = _count = pts; + clearSkippedUpdates(); + } + bool inited() const { + return _good > 0; + } + void setRequesting(bool isRequesting) { + _requesting = isRequesting; + if (_requesting) { + clearSkippedUpdates(); + } + } + bool requesting() const { + return _requesting; + } + bool waitingForSkipped() const { + return _waitingForSkipped; + } + bool waitingForShortPoll() const { + return _waitingForShortPoll; + } + void setWaitingForSkipped(ChannelData *channel, int32 ms); // < 0 - not waiting + void setWaitingForShortPoll(ChannelData *channel, int32 ms); // < 0 - not waiting + int32 current() const{ + return _good; + } + bool updated( + ChannelData *channel, + int32 pts, + int32 count, + const MTPUpdates &updates); + bool updated( + ChannelData *channel, + int32 pts, + int32 count, + const MTPUpdate &update); + bool updated( + ChannelData *channel, + int32 pts, + int32 count); + bool updateAndApply( + ChannelData *channel, + int32 pts, + int32 count, + const MTPUpdates &updates); + bool updateAndApply( + ChannelData *channel, + int32 pts, + int32 count, + const MTPUpdate &update); + bool updateAndApply( + ChannelData *channel, + int32 pts, + int32 count); + void applySkippedUpdates(ChannelData *channel); + void clearSkippedUpdates(); + +private: + bool check(ChannelData *channel, int32 pts, int32 count); // return false if need to save that update and apply later + uint64 ptsKey(PtsSkippedQueue queue, int32 pts); + void checkForWaiting(ChannelData *channel); + QMap _queue; + QMap _updateQueue; + QMap _updatesQueue; + int32 _good = 0; + int32 _last = 0; + int32 _count = 0; + int32 _applySkippedLevel = 0; + bool _requesting = false; + bool _waitingForSkipped = false; + bool _waitingForShortPoll = false; + uint32 _skippedKey = 0; +}; + +struct MegagroupInfo { + struct Admin { + explicit Admin(MTPChannelAdminRights rights) + : rights(rights) { + } + Admin(MTPChannelAdminRights rights, bool canEdit) + : rights(rights) + , canEdit(canEdit) { + } + MTPChannelAdminRights rights; + bool canEdit = false; + }; + struct Restricted { + explicit Restricted(MTPChannelBannedRights rights) + : rights(rights) { + } + MTPChannelBannedRights rights; + }; + QList> lastParticipants; + QMap, Admin> lastAdmins; + QMap, Restricted> lastRestricted; + OrderedSet> markupSenders; + OrderedSet> bots; + + UserData *creator = nullptr; // nullptr means unknown + int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other + MsgId pinnedMsgId = 0; + bool joinedMessageFound = false; + MTPInputStickerSet stickerSet = MTP_inputStickerSetEmpty(); + + enum LastParticipantsStatus { + LastParticipantsUpToDate = 0x00, + LastParticipantsAdminsOutdated = 0x01, + LastParticipantsCountOutdated = 0x02, + }; + mutable int lastParticipantsStatus = LastParticipantsUpToDate; + int lastParticipantsCount = 0; + + ChatData *migrateFromPtr = nullptr; + +}; + +class ChannelData : public PeerData { +public: + static constexpr auto kEssentialFlags = 0 + | MTPDchannel::Flag::f_creator + | MTPDchannel::Flag::f_left + | MTPDchannel::Flag::f_broadcast + | MTPDchannel::Flag::f_verified + | MTPDchannel::Flag::f_megagroup + | MTPDchannel::Flag::f_restricted + | MTPDchannel::Flag::f_democracy + | MTPDchannel::Flag::f_signatures + | MTPDchannel::Flag::f_username; + using Flags = Data::Flags< + MTPDchannel::Flags, + kEssentialFlags>; + + static constexpr auto kEssentialFullFlags = 0 + | MTPDchannelFull::Flag::f_can_view_participants + | MTPDchannelFull::Flag::f_can_set_username + | MTPDchannelFull::Flag::f_can_set_stickers; + using FullFlags = Data::Flags< + MTPDchannelFull::Flags, + kEssentialFullFlags>; + + ChannelData(const PeerId &id); + + void setPhoto( + const MTPChatPhoto &photo, + const PhotoId &phId = UnknownPeerPhotoId); + + void setName(const QString &name, const QString &username); + + void setFlags(MTPDchannel::Flags which) { + _flags.set(which); + } + void addFlags(MTPDchannel::Flags which) { + _flags.add(which); + } + void removeFlags(MTPDchannel::Flags which) { + _flags.remove(which); + } + MTPDchannel::Flags flags() const { + return _flags.current(); + } + rpl::producer flagsValue() const { + return _flags.value(); + } + + void setFullFlags(MTPDchannelFull::Flags which) { + _fullFlags.set(which); + } + void addFullFlags(MTPDchannelFull::Flags which) { + _fullFlags.add(which); + } + void removeFullFlags(MTPDchannelFull::Flags which) { + _fullFlags.remove(which); + } + MTPDchannelFull::Flags fullFlags() const { + return _fullFlags.current(); + } + rpl::producer fullFlagsValue() const { + return _fullFlags.value(); + } + + uint64 access = 0; + + MTPinputChannel inputChannel; + + QString username; + + // Returns true if about text was changed. + bool setAbout(const QString &newAbout); + const QString &about() const { + return _about; + } + + int membersCount() const { + return _membersCount; + } + void setMembersCount(int newMembersCount); + + int adminsCount() const { + return _adminsCount; + } + void setAdminsCount(int newAdminsCount); + + int restrictedCount() const { + return _restrictedCount; + } + void setRestrictedCount(int newRestrictedCount); + + int kickedCount() const { + return _kickedCount; + } + void setKickedCount(int newKickedCount); + + bool haveLeft() const { + return flags() & MTPDchannel::Flag::f_left; + } + bool amIn() const { + return !isForbidden() && !haveLeft(); + } + bool addsSignature() const { + return flags() & MTPDchannel::Flag::f_signatures; + } + bool isForbidden() const { + return flags() & MTPDchannel_ClientFlag::f_forbidden; + } + bool isVerified() const { + return flags() & MTPDchannel::Flag::f_verified; + } + + static MTPChannelBannedRights KickedRestrictedRights(); + static constexpr auto kRestrictUntilForever = TimeId(INT_MAX); + static bool IsRestrictedForever(TimeId until) { + return !until || (until == kRestrictUntilForever); + } + void applyEditAdmin( + not_null user, + const MTPChannelAdminRights &oldRights, + const MTPChannelAdminRights &newRights); + void applyEditBanned( + not_null user, + const MTPChannelBannedRights &oldRights, + const MTPChannelBannedRights &newRights); + + int32 date = 0; + int version = 0; + std::unique_ptr mgInfo; + bool lastParticipantsCountOutdated() const { + if (!mgInfo + || !(mgInfo->lastParticipantsStatus + & MegagroupInfo::LastParticipantsCountOutdated)) { + return false; + } + if (mgInfo->lastParticipantsCount == membersCount()) { + mgInfo->lastParticipantsStatus + &= ~MegagroupInfo::LastParticipantsCountOutdated; + return false; + } + return true; + } + bool isMegagroup() const { + return flags() & MTPDchannel::Flag::f_megagroup; + } + bool isBroadcast() const { + return flags() & MTPDchannel::Flag::f_broadcast; + } + bool isPublic() const { + return flags() & MTPDchannel::Flag::f_username; + } + bool amCreator() const { + return flags() & MTPDchannel::Flag::f_creator; + } + const MTPChannelAdminRights &adminRightsBoxed() const { + return _adminRights; + } + const MTPDchannelAdminRights &adminRights() const { + return _adminRights.c_channelAdminRights(); + } + void setAdminRights(const MTPChannelAdminRights &rights); + bool hasAdminRights() const { + return (adminRights().vflags.v != 0); + } + const MTPChannelBannedRights &restrictedRightsBoxed() const { + return _restrictedRights; + } + const MTPDchannelBannedRights &restrictedRights() const { + return _restrictedRights.c_channelBannedRights(); + } + void setRestrictedRights(const MTPChannelBannedRights &rights); + bool hasRestrictedRights() const { + return (restrictedRights().vflags.v != 0); + } + bool hasRestrictedRights(int32 now) const { + return hasRestrictedRights() + && (restrictedRights().vuntil_date.v > now); + } + bool canBanMembers() const { + return adminRights().is_ban_users() || amCreator(); + } + bool canEditMessages() const { + return adminRights().is_edit_messages() || amCreator(); + } + bool canDeleteMessages() const { + return adminRights().is_delete_messages() || amCreator(); + } + bool anyoneCanAddMembers() const { + return (flags() & MTPDchannel::Flag::f_democracy); + } + bool canAddMembers() const { + return adminRights().is_invite_users() + || amCreator() + || (anyoneCanAddMembers() + && amIn() + && !hasRestrictedRights()); + } + bool canAddAdmins() const { + return adminRights().is_add_admins() || amCreator(); + } + bool canPinMessages() const { + return adminRights().is_pin_messages() || amCreator(); + } + bool canPublish() const { + return adminRights().is_post_messages() || amCreator(); + } + bool canWrite() const { + return amIn() + && (canPublish() + || (!isBroadcast() + && !restrictedRights().is_send_messages())); + } + bool canViewMembers() const { + return fullFlags() + & MTPDchannelFull::Flag::f_can_view_participants; + } + bool canViewAdmins() const { + return (isMegagroup() || hasAdminRights() || amCreator()); + } + bool canViewBanned() const { + return (hasAdminRights() || amCreator()); + } + bool canEditInformation() const { + return adminRights().is_change_info() || amCreator(); + } + bool canEditUsername() const { + return amCreator() + && (fullFlags() + & MTPDchannelFull::Flag::f_can_set_username); + } + bool canEditStickers() const { + return (fullFlags() + & MTPDchannelFull::Flag::f_can_set_stickers); + } + bool canDelete() const { + constexpr auto kDeleteChannelMembersLimit = 1000; + return amCreator() + && (membersCount() <= kDeleteChannelMembersLimit); + } + bool canEditAdmin(not_null user) const; + bool canRestrictUser(not_null user) const; + + void setInviteLink(const QString &newInviteLink); + QString inviteLink() const { + return _inviteLink; + } + bool canHaveInviteLink() const { + return adminRights().is_invite_link() || amCreator(); + } + + int32 inviter = 0; // > 0 - user who invited me to channel, < 0 - not in channel + QDateTime inviteDate; + + void ptsInit(int32 pts) { + _ptsWaiter.init(pts); + } + void ptsReceived(int32 pts) { + _ptsWaiter.updateAndApply(this, pts, 0); + } + bool ptsUpdateAndApply(int32 pts, int32 count) { + return _ptsWaiter.updateAndApply(this, pts, count); + } + bool ptsUpdateAndApply( + int32 pts, + int32 count, + const MTPUpdate &update) { + return _ptsWaiter.updateAndApply(this, pts, count, update); + } + bool ptsUpdateAndApply( + int32 pts, + int32 count, + const MTPUpdates &updates) { + return _ptsWaiter.updateAndApply(this, pts, count, updates); + } + int32 pts() const { + return _ptsWaiter.current(); + } + bool ptsInited() const { + return _ptsWaiter.inited(); + } + bool ptsRequesting() const { + return _ptsWaiter.requesting(); + } + void ptsSetRequesting(bool isRequesting) { + return _ptsWaiter.setRequesting(isRequesting); + } + void ptsWaitingForShortPoll(int32 ms) { // < 0 - not waiting + return _ptsWaiter.setWaitingForShortPoll(this, ms); + } + bool ptsWaitingForSkipped() const { + return _ptsWaiter.waitingForSkipped(); + } + bool ptsWaitingForShortPoll() const { + return _ptsWaiter.waitingForShortPoll(); + } + + QString restrictionReason() const override { + return _restrictionReason; + } + void setRestrictionReason(const QString &reason); + +private: + void flagsUpdated(MTPDchannel::Flags diff); + void fullFlagsUpdated(MTPDchannelFull::Flags diff); + + bool canNotEditLastAdmin(not_null user) const; + + Flags _flags = Flags(MTPDchannel_ClientFlag::f_forbidden | 0); + FullFlags _fullFlags; + + PtsWaiter _ptsWaiter; + + int _membersCount = 1; + int _adminsCount = 1; + int _restrictedCount = 0; + int _kickedCount = 0; + + MTPChannelAdminRights _adminRights + = MTP_channelAdminRights(MTP_flags(0)); + MTPChannelBannedRights _restrictedRights + = MTP_channelBannedRights(MTP_flags(0), MTP_int(0)); + + QString _restrictionReason; + QString _about; + + QString _inviteLink; + + rpl::lifetime _lifetime; + +}; + +inline bool isUser(const PeerData *peer) { + return peer ? peer->isUser() : false; +} +inline UserData *PeerData::asUser() { + return isUser() ? static_cast(this) : nullptr; +} +inline UserData *asUser(PeerData *peer) { + return peer ? peer->asUser() : nullptr; +} +inline const UserData *PeerData::asUser() const { + return isUser() ? static_cast(this) : nullptr; +} +inline const UserData *asUser(const PeerData *peer) { + return peer ? peer->asUser() : nullptr; +} +inline bool isChat(const PeerData *peer) { + return peer ? peer->isChat() : false; +} +inline ChatData *PeerData::asChat() { + return isChat() ? static_cast(this) : nullptr; +} +inline ChatData *asChat(PeerData *peer) { + return peer ? peer->asChat() : nullptr; +} +inline const ChatData *PeerData::asChat() const { + return isChat() ? static_cast(this) : nullptr; +} +inline const ChatData *asChat(const PeerData *peer) { + return peer ? peer->asChat() : nullptr; +} +inline bool isChannel(const PeerData *peer) { + return peer ? peer->isChannel() : false; +} +inline ChannelData *PeerData::asChannel() { + return isChannel() ? static_cast(this) : nullptr; +} +inline ChannelData *asChannel(PeerData *peer) { + return peer ? peer->asChannel() : nullptr; +} +inline const ChannelData *PeerData::asChannel() const { + return isChannel() + ? static_cast(this) + : nullptr; +} +inline const ChannelData *asChannel(const PeerData *peer) { + return peer ? peer->asChannel() : nullptr; +} +inline ChannelData *PeerData::asMegagroup() { + return isMegagroup() ? static_cast(this) : nullptr; +} +inline ChannelData *asMegagroup(PeerData *peer) { + return peer ? peer->asMegagroup() : nullptr; +} +inline const ChannelData *PeerData::asMegagroup() const { + return isMegagroup() + ? static_cast(this) + : nullptr; +} +inline const ChannelData *asMegagroup(const PeerData *peer) { + return peer ? peer->asMegagroup() : nullptr; +} +inline bool isMegagroup(const PeerData *peer) { + return peer ? peer->isMegagroup() : false; +} +inline ChatData *PeerData::migrateFrom() const { + return (isMegagroup() && asChannel()->amIn()) + ? asChannel()->mgInfo->migrateFromPtr + : nullptr; +} +inline ChannelData *PeerData::migrateTo() const { + return (isChat() + && asChat()->migrateToPtr + && asChat()->migrateToPtr->amIn()) + ? asChat()->migrateToPtr + : nullptr; +} +inline const Text &PeerData::dialogName() const { + return migrateTo() + ? migrateTo()->dialogName() + : (isUser() && !asUser()->phoneText.isEmpty()) + ? asUser()->phoneText + : nameText; +} +inline const QString &PeerData::shortName() const { + return isUser() ? asUser()->firstName : name; +} +inline const QString &PeerData::userName() const { + return isUser() + ? asUser()->username + : isChannel() + ? asChannel()->username + : emptyUsername(); +} +inline bool PeerData::isVerified() const { + return isUser() + ? asUser()->isVerified() + : isChannel() + ? asChannel()->isVerified() + : false; +} +inline bool PeerData::isMegagroup() const { + return isChannel() ? asChannel()->isMegagroup() : false; +} +inline bool PeerData::canWrite() const { + return isChannel() + ? asChannel()->canWrite() + : isChat() + ? asChat()->canWrite() + : isUser() + ? asUser()->canWrite() + : false; +} diff --git a/Telegram/SourceFiles/data/data_photo.cpp b/Telegram/SourceFiles/data/data_photo.cpp new file mode 100644 index 000000000..3862d8f88 --- /dev/null +++ b/Telegram/SourceFiles/data/data_photo.cpp @@ -0,0 +1,164 @@ +/* +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 "data/data_photo.h" + +//#include "lang/lang_keys.h" +//#include "inline_bots/inline_bot_layout_item.h" +//#include "observer_peer.h" +#include "mainwidget.h" +//#include "application.h" +//#include "storage/file_upload.h" +//#include "mainwindow.h" +//#include "core/file_utilities.h" +//#include "apiwrap.h" +//#include "boxes/confirm_box.h" +//#include "media/media_audio.h" +//#include "storage/localstorage.h" +#include "history/history_media_types.h" +//#include "styles/style_history.h" +//#include "window/themes/window_theme.h" +//#include "auth_session.h" +#include "messenger.h" +//#include "storage/file_download.h" + +PhotoData::PhotoData(const PhotoId &id, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) +: id(id) +, access(access) +, date(date) +, thumb(thumb) +, medium(medium) +, full(full) { +} + +void PhotoData::automaticLoad(const HistoryItem *item) { + full->automaticLoad(item); +} + +void PhotoData::automaticLoadSettingsChanged() { + full->automaticLoadSettingsChanged(); +} + +void PhotoData::download() { + full->loadEvenCancelled(); + notifyLayoutChanged(); +} + +bool PhotoData::loaded() const { + bool wasLoading = loading(); + if (full->loaded()) { + if (wasLoading) { + notifyLayoutChanged(); + } + return true; + } + return false; +} + +bool PhotoData::loading() const { + return full->loading(); +} + +bool PhotoData::displayLoading() const { + return full->loading() ? full->displayLoading() : uploading(); +} + +void PhotoData::cancel() { + full->cancel(); + notifyLayoutChanged(); +} + +void PhotoData::notifyLayoutChanged() const { + auto &items = App::photoItems(); + auto i = items.constFind(const_cast(this)); + if (i != items.cend()) { + for_const (auto item, i.value()) { + Notify::historyItemLayoutChanged(item); + } + } +} + +float64 PhotoData::progress() const { + if (uploading()) { + if (uploadingData->size > 0) { + return float64(uploadingData->offset) / uploadingData->size; + } + return 0; + } + return full->progress(); +} + +int32 PhotoData::loadOffset() const { + return full->loadOffset(); +} + +bool PhotoData::uploading() const { + return !!uploadingData; +} + +void PhotoData::forget() { + thumb->forget(); + replyPreview->forget(); + medium->forget(); + full->forget(); +} + +ImagePtr PhotoData::makeReplyPreview() { + if (replyPreview->isNull() && !thumb->isNull()) { + if (thumb->loaded()) { + int w = thumb->width(), h = thumb->height(); + if (w <= 0) w = 1; + if (h <= 0) h = 1; + replyPreview = ImagePtr(w > h ? thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : thumb->pix(st::msgReplyBarSize.height()), "PNG"); + } else { + thumb->load(); + } + } + return replyPreview; +} + +void PhotoOpenClickHandler::onClickImpl() const { + Messenger::Instance().showPhoto(this, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem()); +} + +void PhotoSaveClickHandler::onClickImpl() const { + auto data = photo(); + if (!data->date) return; + + data->download(); +} + +void PhotoCancelClickHandler::onClickImpl() const { + auto data = photo(); + if (!data->date) return; + + if (data->uploading()) { + if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) { + if (auto media = item->getMedia()) { + if (media->type() == MediaTypePhoto && static_cast(media)->photo() == data) { + App::contextItem(item); + App::main()->cancelUploadLayer(); + } + } + } + } else { + data->cancel(); + } +} diff --git a/Telegram/SourceFiles/data/data_photo.h b/Telegram/SourceFiles/data/data_photo.h new file mode 100644 index 000000000..3fea502d5 --- /dev/null +++ b/Telegram/SourceFiles/data/data_photo.h @@ -0,0 +1,118 @@ +/* +This file is part of Telegram Desktop, +the official desktop version of Telegram messaging app, see https://telegram.org + +Telegram Desktop is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +It is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +In addition, as a special exception, the copyright holders give permission +to link the code of portions of this program with the OpenSSL library. + +Full license: https://github.com/telegramdesktop/tdesktop/blob/master/LICENSE +Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org +*/ +#pragma once + +#include "data/data_types.h" + +class PhotoData { +public: + PhotoData( + const PhotoId &id, + const uint64 &access = 0, + int32 date = 0, + const ImagePtr &thumb = ImagePtr(), + const ImagePtr &medium = ImagePtr(), + const ImagePtr &full = ImagePtr()); + + void automaticLoad(const HistoryItem *item); + void automaticLoadSettingsChanged(); + + void download(); + bool loaded() const; + bool loading() const; + bool displayLoading() const; + void cancel(); + float64 progress() const; + int32 loadOffset() const; + bool uploading() const; + + void forget(); + ImagePtr makeReplyPreview(); + + PhotoId id; + uint64 access; + int32 date; + ImagePtr thumb, replyPreview; + ImagePtr medium; + ImagePtr full; + + PeerData *peer = nullptr; // for chat and channel photos connection + // geo, caption + + struct UploadingData { + UploadingData(int size) : size(size) { + } + int offset = 0; + int size = 0; + }; + std::unique_ptr uploadingData; + +private: + void notifyLayoutChanged() const; + +}; + +class PhotoClickHandler : public LeftButtonClickHandler { +public: + PhotoClickHandler( + not_null photo, + PeerData *peer = nullptr) + : _photo(photo), _peer(peer) { + } + not_null photo() const { + return _photo; + } + PeerData *peer() const { + return _peer; + } + +private: + not_null _photo; + PeerData *_peer; + +}; + +class PhotoOpenClickHandler : public PhotoClickHandler { +public: + using PhotoClickHandler::PhotoClickHandler; + +protected: + void onClickImpl() const override; + +}; + +class PhotoSaveClickHandler : public PhotoClickHandler { +public: + using PhotoClickHandler::PhotoClickHandler; + +protected: + void onClickImpl() const override; + +}; + +class PhotoCancelClickHandler : public PhotoClickHandler { +public: + using PhotoClickHandler::PhotoClickHandler; + +protected: + void onClickImpl() const override; + +}; diff --git a/Telegram/SourceFiles/data/data_types.cpp b/Telegram/SourceFiles/data/data_types.cpp new file mode 100644 index 000000000..aa0ebe628 --- /dev/null +++ b/Telegram/SourceFiles/data/data_types.cpp @@ -0,0 +1,55 @@ +/* +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 "data/data_types.h" + +#include "data/data_document.h" + +void AudioMsgId::setTypeFromAudio() { + if (_audio->voice() || _audio->isRoundVideo()) { + _type = Type::Voice; + } else if (_audio->isVideo()) { + _type = Type::Video; + } else if (_audio->tryPlaySong()) { + _type = Type::Song; + } else { + _type = Type::Unknown; + } +} + +void MessageCursor::fillFrom(const QTextEdit *edit) { + QTextCursor c = edit->textCursor(); + position = c.position(); + anchor = c.anchor(); + QScrollBar *s = edit->verticalScrollBar(); + scroll = (s && (s->value() != s->maximum())) + ? s->value() + : QFIXED_MAX; +} + +void MessageCursor::applyTo(QTextEdit *edit) { + auto cursor = edit->textCursor(); + cursor.setPosition(anchor, QTextCursor::MoveAnchor); + cursor.setPosition(position, QTextCursor::KeepAnchor); + edit->setTextCursor(cursor); + if (auto scrollbar = edit->verticalScrollBar()) { + scrollbar->setValue(scroll); + } +} diff --git a/Telegram/SourceFiles/data/data_types.h b/Telegram/SourceFiles/data/data_types.h new file mode 100644 index 000000000..b2338cd10 --- /dev/null +++ b/Telegram/SourceFiles/data/data_types.h @@ -0,0 +1,390 @@ +/* +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 + +class PeerData; +class UserData; +class ChatData; +class ChannelData; + +using UserId = int32; +using ChatId = int32; +using ChannelId = int32; + +constexpr auto NoChannel = ChannelId(0); + +using PeerId = uint64; + +constexpr auto PeerIdMask = PeerId(0xFFFFFFFFULL); +constexpr auto PeerIdTypeMask = PeerId(0x300000000ULL); +constexpr auto PeerIdUserShift = PeerId(0x000000000ULL); +constexpr auto PeerIdChatShift = PeerId(0x100000000ULL); +constexpr auto PeerIdChannelShift = PeerId(0x200000000ULL); + +inline bool peerIsUser(const PeerId &id) { + return (id & PeerIdTypeMask) == PeerIdUserShift; +} +inline bool peerIsChat(const PeerId &id) { + return (id & PeerIdTypeMask) == PeerIdChatShift; +} +inline bool peerIsChannel(const PeerId &id) { + return (id & PeerIdTypeMask) == PeerIdChannelShift; +} +inline PeerId peerFromUser(UserId user_id) { + return PeerIdUserShift | uint64(uint32(user_id)); +} +inline PeerId peerFromChat(ChatId chat_id) { + return PeerIdChatShift | uint64(uint32(chat_id)); +} +inline PeerId peerFromChannel(ChannelId channel_id) { + return PeerIdChannelShift | uint64(uint32(channel_id)); +} +inline PeerId peerFromUser(const MTPint &user_id) { + return peerFromUser(user_id.v); +} +inline PeerId peerFromChat(const MTPint &chat_id) { + return peerFromChat(chat_id.v); +} +inline PeerId peerFromChannel(const MTPint &channel_id) { + return peerFromChannel(channel_id.v); +} +inline int32 peerToBareInt(const PeerId &id) { + return int32(uint32(id & PeerIdMask)); +} +inline UserId peerToUser(const PeerId &id) { + return peerIsUser(id) ? peerToBareInt(id) : 0; +} +inline ChatId peerToChat(const PeerId &id) { + return peerIsChat(id) ? peerToBareInt(id) : 0; +} +inline ChannelId peerToChannel(const PeerId &id) { + return peerIsChannel(id) ? peerToBareInt(id) : NoChannel; +} +inline MTPint peerToBareMTPInt(const PeerId &id) { + return MTP_int(peerToBareInt(id)); +} +inline PeerId peerFromMTP(const MTPPeer &peer) { + switch (peer.type()) { + case mtpc_peerUser: return peerFromUser(peer.c_peerUser().vuser_id); + case mtpc_peerChat: return peerFromChat(peer.c_peerChat().vchat_id); + case mtpc_peerChannel: return peerFromChannel(peer.c_peerChannel().vchannel_id); + } + return 0; +} +inline MTPpeer peerToMTP(const PeerId &id) { + if (peerIsUser(id)) { + return MTP_peerUser(peerToBareMTPInt(id)); + } else if (peerIsChat(id)) { + return MTP_peerChat(peerToBareMTPInt(id)); + } else if (peerIsChannel(id)) { + return MTP_peerChannel(peerToBareMTPInt(id)); + } + return MTP_peerUser(MTP_int(0)); +} + +using MsgId = int32; +constexpr auto StartClientMsgId = MsgId(-0x7FFFFFFF); +constexpr auto EndClientMsgId = MsgId(-0x40000000); +constexpr auto ShowAtTheEndMsgId = MsgId(-0x40000000); +constexpr auto SwitchAtTopMsgId = MsgId(-0x3FFFFFFF); +constexpr auto ShowAtProfileMsgId = MsgId(-0x3FFFFFFE); +constexpr auto ShowAndStartBotMsgId = MsgId(-0x3FFFFFD); +constexpr auto ShowAtGameShareMsgId = MsgId(-0x3FFFFFC); +constexpr auto ServerMaxMsgId = MsgId(0x3FFFFFFF); +constexpr auto ShowAtUnreadMsgId = MsgId(0); +constexpr inline bool IsClientMsgId(MsgId id) { + return (id >= StartClientMsgId && id < EndClientMsgId); +} +constexpr inline bool IsServerMsgId(MsgId id) { + return (id > 0 && id < ServerMaxMsgId); +} + +struct MsgRange { + MsgRange() = default; + MsgRange(MsgId from, MsgId till) : from(from), till(till) { + } + + MsgId from = 0; + MsgId till = 0; +}; +inline bool operator==(const MsgRange &a, const MsgRange &b) { + return (a.from == b.from) && (a.till == b.till); +} +inline bool operator!=(const MsgRange &a, const MsgRange &b) { + return !(a == b); +} + +struct FullMsgId { + FullMsgId() = default; + FullMsgId(ChannelId channel, MsgId msg) : channel(channel), msg(msg) { + } + explicit operator bool() const { + return msg != 0; + } + ChannelId channel = NoChannel; + MsgId msg = 0; +}; +inline bool operator==(const FullMsgId &a, const FullMsgId &b) { + return (a.channel == b.channel) && (a.msg == b.msg); +} +inline bool operator!=(const FullMsgId &a, const FullMsgId &b) { + return !(a == b); +} +inline bool operator<(const FullMsgId &a, const FullMsgId &b) { + if (a.msg < b.msg) return true; + if (a.msg > b.msg) return false; + return a.channel < b.channel; +} + +inline PeerId peerFromMessage(const MTPmessage &msg) { + auto compute = [](auto &message) { + auto from_id = message.has_from_id() ? peerFromUser(message.vfrom_id) : 0; + auto to_id = peerFromMTP(message.vto_id); + auto out = message.is_out(); + return (out || !peerIsUser(to_id)) ? to_id : from_id; + }; + switch (msg.type()) { + case mtpc_message: return compute(msg.c_message()); + case mtpc_messageService: return compute(msg.c_messageService()); + } + return 0; +} +inline MTPDmessage::Flags flagsFromMessage(const MTPmessage &msg) { + switch (msg.type()) { + case mtpc_message: return msg.c_message().vflags.v; + case mtpc_messageService: return mtpCastFlags(msg.c_messageService().vflags.v); + } + return 0; +} +inline MsgId idFromMessage(const MTPmessage &msg) { + switch (msg.type()) { + case mtpc_messageEmpty: return msg.c_messageEmpty().vid.v; + case mtpc_message: return msg.c_message().vid.v; + case mtpc_messageService: return msg.c_messageService().vid.v; + } + Unexpected("Type in idFromMessage()"); +} +inline TimeId dateFromMessage(const MTPmessage &msg) { + switch (msg.type()) { + case mtpc_message: return msg.c_message().vdate.v; + case mtpc_messageService: return msg.c_messageService().vdate.v; + } + return 0; +} + +class DocumentData; +class PhotoData; +struct WebPageData; +struct GameData; + +class AudioMsgId; +class PhotoClickHandler; +class PhotoOpenClickHandler; +class PhotoSaveClickHandler; +class PhotoCancelClickHandler; +class DocumentClickHandler; +class DocumentSaveClickHandler; +class DocumentOpenClickHandler; +class DocumentCancelClickHandler; +class GifOpenClickHandler; +class VoiceSeekClickHandler; + +using PhotoId = uint64; +using VideoId = uint64; +using AudioId = uint64; +using DocumentId = uint64; +using WebPageId = uint64; +using GameId = uint64; +constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL); + +using PreparedPhotoThumbs = QMap; + +// [0] == -1 -- counting, [0] == -2 -- could not count +using VoiceWaveform = QVector; + +enum ActionOnLoad { + ActionOnLoadNone, + ActionOnLoadOpen, + ActionOnLoadOpenWith, + ActionOnLoadPlayInline +}; + +enum LocationType { + UnknownFileLocation = 0, + // 1, 2, etc are used as "version" value in mediaKey() method. + + DocumentFileLocation = 0x4e45abe9, // mtpc_inputDocumentFileLocation + AudioFileLocation = 0x74dc404d, // mtpc_inputAudioFileLocation + VideoFileLocation = 0x3d0364ec, // mtpc_inputVideoFileLocation +}; + +enum FileStatus { + FileDownloadFailed = -2, + FileUploadFailed = -1, + FileUploading = 0, + FileReady = 1, +}; + +// Don't change the values. This type is used for serialization. +enum DocumentType { + FileDocument = 0, + VideoDocument = 1, + SongDocument = 2, + StickerDocument = 3, + AnimatedDocument = 4, + VoiceDocument = 5, + RoundVideoDocument = 6, +}; + +using MediaKey = QPair; + +class AudioMsgId { +public: + enum class Type { + Unknown, + Voice, + Song, + Video, + }; + + AudioMsgId() = default; + AudioMsgId( + DocumentData *audio, + const FullMsgId &msgId, + uint32 playId = 0) + : _audio(audio) + , _contextId(msgId) + , _playId(playId) { + setTypeFromAudio(); + } + + Type type() const { + return _type; + } + DocumentData *audio() const { + return _audio; + } + FullMsgId contextId() const { + return _contextId; + } + uint32 playId() const { + return _playId; + } + + explicit operator bool() const { + return _audio != nullptr; + } + +private: + void setTypeFromAudio(); + + DocumentData *_audio = nullptr; + Type _type = Type::Unknown; + FullMsgId _contextId; + uint32 _playId = 0; + +}; + +inline bool operator<(const AudioMsgId &a, const AudioMsgId &b) { + if (quintptr(a.audio()) < quintptr(b.audio())) { + return true; + } else if (quintptr(b.audio()) < quintptr(a.audio())) { + return false; + } else if (a.contextId() < b.contextId()) { + return true; + } else if (b.contextId() < a.contextId()) { + return false; + } + return (a.playId() < b.playId()); +} + +inline bool operator==(const AudioMsgId &a, const AudioMsgId &b) { + return (a.audio() == b.audio()) + && (a.contextId() == b.contextId()) + && (a.playId() == b.playId()); +} + +inline bool operator!=(const AudioMsgId &a, const AudioMsgId &b) { + return !(a == b); +} + +inline MsgId clientMsgId() { + static MsgId CurrentClientMsgId = StartClientMsgId; + Assert(CurrentClientMsgId < EndClientMsgId); + return CurrentClientMsgId++; +} + +struct MessageCursor { + MessageCursor() = default; + MessageCursor(int position, int anchor, int scroll) + : position(position) + , anchor(anchor) + , scroll(scroll) { + } + MessageCursor(const QTextEdit *edit) { + fillFrom(edit); + } + + void fillFrom(const QTextEdit *edit); + void applyTo(QTextEdit *edit); + + int position = 0; + int anchor = 0; + int scroll = QFIXED_MAX; + +}; + +inline bool operator==( + const MessageCursor &a, + const MessageCursor &b) { + return (a.position == b.position) + && (a.anchor == b.anchor) + && (a.scroll == b.scroll); +} + +struct SendAction { + enum class Type { + Typing, + RecordVideo, + UploadVideo, + RecordVoice, + UploadVoice, + RecordRound, + UploadRound, + UploadPhoto, + UploadFile, + ChooseLocation, + ChooseContact, + PlayGame, + }; + SendAction( + Type type, + TimeMs until, + int progress = 0) + : type(type) + , until(until) + , progress(progress) { + } + Type type = Type::Typing; + TimeMs until = 0; + int progress = 0; + +}; diff --git a/Telegram/SourceFiles/data/data_web_page.h b/Telegram/SourceFiles/data/data_web_page.h new file mode 100644 index 000000000..ccf3a0147 --- /dev/null +++ b/Telegram/SourceFiles/data/data_web_page.h @@ -0,0 +1,88 @@ +/* +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 "data/data_photo.h" +#include "data/data_document.h" + +enum WebPageType { + WebPagePhoto, + WebPageVideo, + WebPageProfile, + WebPageArticle +}; + +inline WebPageType toWebPageType(const QString &type) { + if (type == qstr("photo")) return WebPagePhoto; + if (type == qstr("video")) return WebPageVideo; + if (type == qstr("profile")) return WebPageProfile; + return WebPageArticle; +} + +struct WebPageData { + WebPageData(const WebPageId &id) : id(id) { + } + WebPageData( + const WebPageId &id, + WebPageType type, + const QString &url, + const QString &displayUrl, + const QString &siteName, + const QString &title, + const TextWithEntities &description, + DocumentData *document, + PhotoData *photo, + int32 duration, + const QString &author, + int32 pendingTill) + : id(id) + , type(type) + , url(url) + , displayUrl(displayUrl) + , siteName(siteName) + , title(title) + , description(description) + , duration(duration) + , author(author) + , photo(photo) + , document(document) + , pendingTill(pendingTill) { + } + + void forget() { + if (document) document->forget(); + if (photo) photo->forget(); + } + + WebPageId id = 0; + WebPageType type = WebPageArticle; + QString url; + QString displayUrl; + QString siteName; + QString title; + TextWithEntities description; + int32 duration = 0; + QString author; + PhotoData *photo = nullptr; + DocumentData *document = nullptr; + int32 pendingTill = 0; + +}; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index cc8d82099..463f0e85a 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -291,9 +291,12 @@ void showPeerHistory( ShowWay way, anim::type animated, anim::activation activation) { + auto ms = getms(); + LOG(("Show Peer Start")); if (MainWidget *m = App::main()) { m->ui_showPeerHistory(peer, msgId, way, animated, activation); } + LOG(("Show Peer End: %1").arg(getms() - ms)); } void showPeerHistoryAsync(const PeerId &peer, MsgId msgId, ShowWay way) { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 2bfc72eac..2382e8382 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -1051,8 +1051,9 @@ HistoryItem *History::createItem(const MTPMessage &msg, bool applyServiceAction, } break; case mtpc_messageActionChatMigrateTo: { - peer->asChat()->flags |= MTPDchat::Flag::f_deactivated; - + if (auto chat = peer->asChat()) { + chat->addFlags(MTPDchat::Flag::f_deactivated); + } //auto &d = action.c_messageActionChatMigrateTo(); //auto channel = App::channelLoaded(d.vchannel_id.v); } break; diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index cc07761cd..be31a550e 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -20,7 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "structs.h" +#include "data/data_types.h" +#include "data/data_peer.h" #include "dialogs/dialogs_common.h" #include "ui/effects/send_action_animations.h" #include "base/observer.h" diff --git a/Telegram/SourceFiles/history/history_admin_log_item.cpp b/Telegram/SourceFiles/history/history_admin_log_item.cpp index 2586bc8f3..34c766227 100644 --- a/Telegram/SourceFiles/history/history_admin_log_item.cpp +++ b/Telegram/SourceFiles/history/history_admin_log_item.cpp @@ -188,7 +188,14 @@ auto GenerateUserString(MTPint userId) { // User name in "User name (@username)" format with entities. auto user = App::user(userId.v); auto name = TextWithEntities { App::peerName(user) }; - name.entities.push_back(EntityInText(EntityInTextMentionName, 0, name.text.size(), QString::number(user->id) + '.' + QString::number(user->access))); + auto entityData = QString::number(user->id) + + '.' + + QString::number(user->accessHash()); + name.entities.push_back(EntityInText( + EntityInTextMentionName, + 0, + name.text.size(), + entityData)); auto username = user->userName(); if (username.isEmpty()) { return name; diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h index 5866cfdca..155ecb948 100644 --- a/Telegram/SourceFiles/history/history_media_types.h +++ b/Telegram/SourceFiles/history/history_media_types.h @@ -22,6 +22,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "history/history_media.h" #include "ui/effects/radial_animation.h" +#include "data/data_document.h" +#include "data/data_photo.h" +#include "data/data_web_page.h" +#include "data/data_game.h" namespace Media { namespace Clip { diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 11391f9c9..a4bd717d6 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -970,7 +970,10 @@ void HistoryWidget::onMentionInsert(UserData *user) { if (replacement.isEmpty()) { replacement = App::peerName(user); } - entityTag = qsl("mention://user.") + QString::number(user->bareId()) + '.' + QString::number(user->access); + entityTag = qsl("mention://user.") + + QString::number(user->bareId()) + + '.' + + QString::number(user->accessHash()); } else { replacement = '@' + user->username; } diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index e0494c34f..a8674833d 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -22,6 +22,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include #include +#include "data/data_photo.h" #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" #include "styles/style_info.h" diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 078dad98d..861b338e8 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "inline_bots/inline_bot_layout_internal.h" +#include "data/data_photo.h" +#include "data/data_document.h" #include "styles/style_overview.h" #include "styles/style_history.h" #include "styles/style_chat_helpers.h" diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp index 3ae0e1da3..addcc43cb 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "inline_bots/inline_bot_layout_item.h" +#include "data/data_photo.h" +#include "data/data_document.h" #include "core/click_handler_types.h" #include "inline_bots/inline_bot_result.h" #include "inline_bots/inline_bot_layout_internal.h" diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h index f817b458a..c05b9cbc9 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_item.h @@ -21,7 +21,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "layout.h" -#include "structs.h" #include "ui/text/text.h" namespace InlineBots { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp index 1741e95d8..c4b3af240 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "inline_bots/inline_bot_result.h" +#include "data/data_photo.h" +#include "data/data_document.h" #include "inline_bots/inline_bot_layout_item.h" #include "inline_bots/inline_bot_send_data.h" #include "storage/file_download.h" diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_result.h b/Telegram/SourceFiles/inline_bots/inline_bot_result.h index 0076c9c0b..c036d3fef 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_result.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_result.h @@ -21,7 +21,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "core/basic_types.h" -#include "structs.h" class FileLoader; diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp index 0db4ddda5..2693e134d 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "inline_bots/inline_bot_send_data.h" +#include "data/data_document.h" #include "inline_bots/inline_bot_result.h" #include "storage/localstorage.h" #include "lang/lang_keys.h" diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h index 4f9b4d493..62f77e6cd 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h +++ b/Telegram/SourceFiles/inline_bots/inline_bot_send_data.h @@ -21,7 +21,6 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #pragma once #include "core/basic_types.h" -#include "structs.h" #include "history/history_location_manager.h" namespace InlineBots { diff --git a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp index 378f05e79..8ba96c2ae 100644 --- a/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_results_widget.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "inline_bots/inline_results_widget.h" +#include "data/data_photo.h" +#include "data/data_document.h" #include "styles/style_chat_helpers.h" #include "ui/widgets/buttons.h" #include "ui/widgets/shadow.h" diff --git a/Telegram/SourceFiles/layerwidget.cpp b/Telegram/SourceFiles/layerwidget.cpp index 4c6bdaed3..25f5cbc0b 100644 --- a/Telegram/SourceFiles/layerwidget.cpp +++ b/Telegram/SourceFiles/layerwidget.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "lang/lang_keys.h" +#include "data/data_photo.h" +#include "data/data_document.h" #include "media/media_clip_reader.h" #include "boxes/abstract_box.h" #include "layerwidget.h" diff --git a/Telegram/SourceFiles/layout.cpp b/Telegram/SourceFiles/layout.cpp index 89e67b03c..b5b3a9141 100644 --- a/Telegram/SourceFiles/layout.cpp +++ b/Telegram/SourceFiles/layout.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "layout.h" +#include "data/data_document.h" #include "lang/lang_keys.h" #include "mainwidget.h" #include "application.h" diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 51ab4dfad..a832c31bb 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -21,6 +21,10 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "mainwidget.h" #include +#include "data/data_photo.h" +#include "data/data_document.h" +#include "data/data_web_page.h" +#include "data/data_game.h" #include "styles/style_dialogs.h" #include "styles/style_history.h" #include "ui/special_buttons.h" @@ -586,7 +590,7 @@ bool MainWidget::setForwardDraft(PeerId peerId, const SelectedItemSet &items) { bool MainWidget::onShareUrl(const PeerId &peer, const QString &url, const QString &text) { PeerData *p = App::peer(peer); - if (!peer || (p->isChannel() && !p->asChannel()->canPublish() && p->asChannel()->isBroadcast()) || (p->isChat() && !p->asChat()->canWrite()) || (p->isUser() && p->asUser()->isInaccessible())) { + if (!peer || p->canWrite()) { Ui::show(Box(lang(lng_share_cant))); return false; } @@ -606,7 +610,7 @@ bool MainWidget::onShareUrl(const PeerId &peer, const QString &url, const QStrin bool MainWidget::onInlineSwitchChosen(const PeerId &peer, const QString &botAndQuery) { PeerData *p = App::peer(peer); - if (!peer || (p->isChannel() && !p->asChannel()->canPublish() && p->asChannel()->isBroadcast()) || (p->isChat() && !p->asChat()->canWrite()) || (p->isUser() && p->asUser()->isInaccessible())) { + if (!peer || p->canWrite()) { Ui::show(Box(lang(lng_inline_switch_cant))); return false; } diff --git a/Telegram/SourceFiles/mainwindow.cpp b/Telegram/SourceFiles/mainwindow.cpp index a00d838a3..866aafa4e 100644 --- a/Telegram/SourceFiles/mainwindow.cpp +++ b/Telegram/SourceFiles/mainwindow.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "mainwindow.h" +#include "data/data_document.h" #include "dialogs/dialogs_layout.h" #include "styles/style_dialogs.h" #include "styles/style_window.h" diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index b0a1ade1e..622f321c6 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "media/media_audio.h" +#include "data/data_document.h" #include "media/media_audio_ffmpeg_loader.h" #include "media/media_child_ffmpeg_loader.h" #include "media/media_audio_loaders.h" diff --git a/Telegram/SourceFiles/media/media_clip_reader.cpp b/Telegram/SourceFiles/media/media_clip_reader.cpp index 7c38913be..0afa5947b 100644 --- a/Telegram/SourceFiles/media/media_clip_reader.cpp +++ b/Telegram/SourceFiles/media/media_clip_reader.cpp @@ -20,7 +20,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "media/media_clip_reader.h" +#include "data/data_document.h" #include "storage/file_download.h" +#include "media/media_clip_ffmpeg.h" +#include "media/media_clip_qtgif.h" +#include "mainwidget.h" +#include "mainwindow.h" extern "C" { #include @@ -29,11 +34,6 @@ extern "C" { #include } -#include "media/media_clip_ffmpeg.h" -#include "media/media_clip_qtgif.h" -#include "mainwidget.h" -#include "mainwindow.h" - namespace Media { namespace Clip { namespace { diff --git a/Telegram/SourceFiles/media/player/media_player_cover.cpp b/Telegram/SourceFiles/media/player/media_player_cover.cpp index fae5c08dc..eea0b06e5 100644 --- a/Telegram/SourceFiles/media/player/media_player_cover.cpp +++ b/Telegram/SourceFiles/media/player/media_player_cover.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "media/player/media_player_cover.h" +#include "data/data_document.h" #include "ui/widgets/labels.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/buttons.h" diff --git a/Telegram/SourceFiles/media/player/media_player_float.cpp b/Telegram/SourceFiles/media/player/media_player_float.cpp index ab2657df5..a5617e624 100644 --- a/Telegram/SourceFiles/media/player/media_player_float.cpp +++ b/Telegram/SourceFiles/media/player/media_player_float.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "media/player/media_player_float.h" +#include "data/data_document.h" #include "styles/style_media_player.h" #include "history/history_media.h" #include "media/media_clip_reader.h" diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 501f89c5c..fce62571a 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "media/player/media_player_instance.h" +#include "data/data_document.h" #include "media/media_audio.h" #include "media/media_audio_capture.h" #include "observer_peer.h" diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 0e3d5a18d..8acb3f77d 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "media/player/media_player_widget.h" +#include "data/data_document.h" #include "ui/widgets/labels.h" #include "ui/widgets/continuous_sliders.h" #include "ui/widgets/shadow.h" diff --git a/Telegram/SourceFiles/messenger.cpp b/Telegram/SourceFiles/messenger.cpp index beef25b51..08489cfbc 100644 --- a/Telegram/SourceFiles/messenger.cpp +++ b/Telegram/SourceFiles/messenger.cpp @@ -20,6 +20,8 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "messenger.h" +#include "data/data_photo.h" +#include "data/data_document.h" #include "base/timer.h" #include "storage/localstorage.h" #include "platform/platform_specific.h" diff --git a/Telegram/SourceFiles/mtproto/core_types.h b/Telegram/SourceFiles/mtproto/core_types.h index 59b81db12..19015c31d 100644 --- a/Telegram/SourceFiles/mtproto/core_types.h +++ b/Telegram/SourceFiles/mtproto/core_types.h @@ -370,10 +370,10 @@ class MTPflags { void read(const mtpPrime *&from, const mtpPrime *end, mtpTypeId cons = mtpc_flags) { if (from + 1 > end) throw mtpErrorInsufficient(); if (cons != mtpc_flags) throw mtpErrorUnexpected(cons, "MTPflags"); - v = static_cast(*(from++)); + v = Flags::from_raw(static_cast(*(from++))); } void write(mtpBuffer &to) const { - to.push_back(static_cast(v)); + to.push_back(static_cast(v.value())); } private: diff --git a/Telegram/SourceFiles/mtproto/dc_options.cpp b/Telegram/SourceFiles/mtproto/dc_options.cpp index f5b0ca0ec..068f3a392 100644 --- a/Telegram/SourceFiles/mtproto/dc_options.cpp +++ b/Telegram/SourceFiles/mtproto/dc_options.cpp @@ -297,7 +297,7 @@ void DcOptions::constructFromSerialized(const QByteArray &serialized) { return; } - applyOneGuarded(DcId(id), MTPDdcOption::Flags(flags), ip, port); + applyOneGuarded(DcId(id), MTPDdcOption::Flags::from_raw(flags), ip, port); } // Read CDN config diff --git a/Telegram/SourceFiles/mtproto/type_utils.h b/Telegram/SourceFiles/mtproto/type_utils.h index b843ba618..def7742c8 100644 --- a/Telegram/SourceFiles/mtproto/type_utils.h +++ b/Telegram/SourceFiles/mtproto/type_utils.h @@ -47,81 +47,108 @@ namespace base {\ } // we use the same flags field for some additional client side flags -enum class MTPDmessage_ClientFlag : int32 { +enum class MTPDmessage_ClientFlag : uint32 { // message has links for "shared links" indexing - f_has_text_links = (1 << 30), + f_has_text_links = (1U << 30), // message is a group migrate (group -> supergroup) service message - f_is_group_migrate = (1 << 29), + f_is_group_migrate = (1U << 29), // message needs initDimensions() + resize() + paint() - f_pending_init_dimensions = (1 << 28), + f_pending_init_dimensions = (1U << 28), // message needs resize() + paint() - f_pending_resize = (1 << 27), + f_pending_resize = (1U << 27), // message needs paint() - f_pending_paint = (1 << 26), + f_pending_paint = (1U << 26), // message is attached to previous one when displaying the history - f_attach_to_previous = (1 << 25), + f_attach_to_previous = (1U << 25), // message is attached to next one when displaying the history - f_attach_to_next = (1 << 24), + f_attach_to_next = (1U << 24), // message was sent from inline bot, need to re-set media when sent - f_from_inline_bot = (1 << 23), + f_from_inline_bot = (1U << 23), // message has a switch inline keyboard button, need to return to inline - f_has_switch_inline_button = (1 << 22), + f_has_switch_inline_button = (1U << 22), // message is generated on the client side and should be unread - f_clientside_unread = (1 << 21), + f_clientside_unread = (1U << 21), // update this when adding new client side flags - MIN_FIELD = (1 << 21), + MIN_FIELD = (1U << 21), }; DEFINE_MTP_CLIENT_FLAGS(MTPDmessage) -enum class MTPDreplyKeyboardMarkup_ClientFlag : int32 { +enum class MTPDreplyKeyboardMarkup_ClientFlag : uint32 { // none (zero) markup - f_zero = (1 << 30), + f_zero = (1U << 30), // markup just wants a text reply - f_force_reply = (1 << 29), + f_force_reply = (1U << 29), // markup keyboard is inline - f_inline = (1 << 28), + f_inline = (1U << 28), // markup has a switch inline keyboard button - f_has_switch_inline_button = (1 << 27), + f_has_switch_inline_button = (1U << 27), // update this when adding new client side flags - MIN_FIELD = (1 << 27), + MIN_FIELD = (1U << 27), }; DEFINE_MTP_CLIENT_FLAGS(MTPDreplyKeyboardMarkup) -enum class MTPDstickerSet_ClientFlag : int32 { +enum class MTPDstickerSet_ClientFlag : uint32 { // old value for sticker set is not yet loaded flag - f_not_loaded__old = (1 << 31), + f_not_loaded__old = (1U << 31), // sticker set is not yet loaded - f_not_loaded = (1 << 30), + f_not_loaded = (1U << 30), // sticker set is one of featured (should be saved locally) - f_featured = (1 << 29), + f_featured = (1U << 29), // sticker set is an unread featured set - f_unread = (1 << 28), + f_unread = (1U << 28), // special set like recent or custom stickers - f_special = (1 << 27), + f_special = (1U << 27), // update this when adding new client side flags - MIN_FIELD = (1 << 27), + MIN_FIELD = (1U << 27), }; DEFINE_MTP_CLIENT_FLAGS(MTPDstickerSet) +enum class MTPDuser_ClientFlag : uint32 { + // forbidden constructor received + f_inaccessible = (1U << 31), + + // update this when adding new client side flags + MIN_FIELD = (1U << 31), +}; +DEFINE_MTP_CLIENT_FLAGS(MTPDuser) + +enum class MTPDchat_ClientFlag : uint32 { + // forbidden constructor received + f_forbidden = (1U << 31), + + // update this when adding new client side flags + MIN_FIELD = (1U << 31), +}; +DEFINE_MTP_CLIENT_FLAGS(MTPDchat) + +enum class MTPDchannel_ClientFlag : uint32 { + // forbidden constructor received + f_forbidden = (1U << 31), + + // update this when adding new client side flags + MIN_FIELD = (1U << 31), +}; +DEFINE_MTP_CLIENT_FLAGS(MTPDchannel) + extern const MTPReplyMarkup MTPnullMarkup; extern const MTPVector MTPnullEntities; extern const MTPMessageFwdHeader MTPnullFwdHeader; diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 9dbd9de6a..7e06009df 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "overview/overview_layout.h" +#include "data/data_document.h" #include "styles/style_overview.h" #include "styles/style_history.h" #include "core/file_utilities.h" @@ -75,6 +76,16 @@ void ItemBase::clickHandlerPressedChanged(const ClickHandlerPtr &action, bool pr Ui::repaintHistoryItem(_parent); } +void RadialProgressItem::setDocumentLinks(DocumentData *document) { + ClickHandlerPtr save; + if (document->voice()) { + save.reset(new DocumentOpenClickHandler(document)); + } else { + save.reset(new DocumentSaveClickHandler(document)); + } + setLinks(MakeShared(document), std::move(save), MakeShared(document)); +} + void RadialProgressItem::clickHandlerActiveChanged(const ClickHandlerPtr &action, bool active) { ItemBase::clickHandlerActiveChanged(action, active); if (action == _openl || action == _savel || action == _cancell) { @@ -461,6 +472,21 @@ void Video::invalidateCache() { _check->invalidateCache(); } } +float64 Video::dataProgress() const { + return _data->progress(); +} + +bool Video::dataFinished() const { + return !_data->loading(); +} + +bool Video::dataLoaded() const { + return _data->loaded(); +} + +bool Video::iconAnimated() const { + return true; +} void Video::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const { bool loaded = _data->loaded(); @@ -642,6 +668,22 @@ void Voice::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint p } } +float64 Voice::dataProgress() const { + return _data->progress(); +} + +bool Voice::dataFinished() const { + return !_data->loading(); +} + +bool Voice::dataLoaded() const { + return _data->loaded(); +} + +bool Voice::iconAnimated() const { + return true; +} + void Voice::updateName() { auto version = 0; if (auto forwarded = _parent->Get()) { @@ -937,6 +979,30 @@ void Document::getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoin } } +float64 Document::dataProgress() const { + return _data->progress(); +} + +bool Document::dataFinished() const { + return !_data->loading(); +} + +bool Document::dataLoaded() const { + return _data->loaded(); +} + +bool Document::iconAnimated() const { + return _data->song() || !_data->loaded() || (_radial && _radial->animating()); +} + +bool Document::withThumb() const { + return !_data->song() + && !_data->thumb->isNull() + && _data->thumb->width() + && _data->thumb->height() + && !documentIsExecutableName(_data->name); +} + bool Document::updateStatusText() { bool showPause = false; int32 statusSize = 0, realDuration = 0; diff --git a/Telegram/SourceFiles/overview/overview_layout.h b/Telegram/SourceFiles/overview/overview_layout.h index c3b28f4ad..6b4816fce 100644 --- a/Telegram/SourceFiles/overview/overview_layout.h +++ b/Telegram/SourceFiles/overview/overview_layout.h @@ -100,15 +100,7 @@ class RadialProgressItem : public ItemBase { protected: ClickHandlerPtr _openl, _savel, _cancell; void setLinks(ClickHandlerPtr &&openl, ClickHandlerPtr &&savel, ClickHandlerPtr &&cancell); - void setDocumentLinks(DocumentData *document) { - ClickHandlerPtr save; - if (document->voice()) { - save.reset(new DocumentOpenClickHandler(document)); - } else { - save.reset(new DocumentSaveClickHandler(document)); - } - setLinks(MakeShared(document), std::move(save), MakeShared(document)); - } + void setDocumentLinks(DocumentData *document); void step_radial(TimeMs ms, bool timer); @@ -219,18 +211,10 @@ class Video : public RadialProgressItem { void invalidateCache() override; protected: - float64 dataProgress() const override { - return _data->progress(); - } - bool dataFinished() const override { - return !_data->loading(); - } - bool dataLoaded() const override { - return _data->loaded(); - } - bool iconAnimated() const override { - return true; - } + float64 dataProgress() const override; + bool dataFinished() const override; + bool dataLoaded() const override; + bool iconAnimated() const override; private: void ensureCheckboxCreated(); @@ -257,18 +241,10 @@ class Voice : public RadialProgressItem { void getState(ClickHandlerPtr &link, HistoryCursorState &cursor, QPoint point) const override; protected: - float64 dataProgress() const override { - return _data->progress(); - } - bool dataFinished() const override { - return !_data->loading(); - } - bool dataLoaded() const override { - return _data->loaded(); - } - bool iconAnimated() const override { - return true; - } + float64 dataProgress() const override; + bool dataFinished() const override; + bool dataLoaded() const override; + bool iconAnimated() const override; private: DocumentData *_data; @@ -298,18 +274,10 @@ class Document : public RadialProgressItem { } protected: - float64 dataProgress() const override { - return _data->progress(); - } - bool dataFinished() const override { - return !_data->loading(); - } - bool dataLoaded() const override { - return _data->loaded(); - } - bool iconAnimated() const override { - return _data->song() || !_data->loaded() || (_radial && _radial->animating()); - } + float64 dataProgress() const override; + bool dataFinished() const override; + bool dataLoaded() const override; + bool iconAnimated() const override; private: DocumentData *_data; @@ -326,9 +294,7 @@ class Document : public RadialProgressItem { int32 _datew, _extw; int32 _thumbw, _colorIndex; - bool withThumb() const { - return !_data->song() && !_data->thumb->isNull() && _data->thumb->width() && _data->thumb->height() && !documentIsExecutableName(_data->name); - } + bool withThumb() const; bool updateStatusText(); }; diff --git a/Telegram/SourceFiles/profile/profile_cover.cpp b/Telegram/SourceFiles/profile/profile_cover.cpp index 8dc839873..4da898b65 100644 --- a/Telegram/SourceFiles/profile/profile_cover.cpp +++ b/Telegram/SourceFiles/profile/profile_cover.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "profile/profile_cover.h" +#include "data/data_photo.h" #include "styles/style_profile.h" #include "styles/style_window.h" #include "profile/profile_cover_drop_area.h" diff --git a/Telegram/SourceFiles/settings.cpp b/Telegram/SourceFiles/settings.cpp index 4306551bd..446c331d9 100644 --- a/Telegram/SourceFiles/settings.cpp +++ b/Telegram/SourceFiles/settings.cpp @@ -21,6 +21,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "settings.h" #include "platform/platform_specific.h" +#include "data/data_document.h" bool gRtl = false; Qt::LayoutDirection gLangDir = gRtl ? Qt::RightToLeft : Qt::LeftToRight; diff --git a/Telegram/SourceFiles/settings/settings_cover.cpp b/Telegram/SourceFiles/settings/settings_cover.cpp index 277d0c66e..53ac27cbd 100644 --- a/Telegram/SourceFiles/settings/settings_cover.cpp +++ b/Telegram/SourceFiles/settings/settings_cover.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "settings/settings_cover.h" +#include "data/data_photo.h" #include "ui/widgets/labels.h" #include "ui/widgets/buttons.h" #include "observer_peer.h" diff --git a/Telegram/SourceFiles/stdafx.h b/Telegram/SourceFiles/stdafx.h index 839c27c02..4f793d111 100644 --- a/Telegram/SourceFiles/stdafx.h +++ b/Telegram/SourceFiles/stdafx.h @@ -103,6 +103,7 @@ namespace func = base::functors; #include "ui/images.h" #include "ui/text/text.h" +#include "data/data_types.h" #include "app.h" #include "facades.h" diff --git a/Telegram/SourceFiles/storage/file_download.cpp b/Telegram/SourceFiles/storage/file_download.cpp index 3b82ee890..f4e00be00 100644 --- a/Telegram/SourceFiles/storage/file_download.cpp +++ b/Telegram/SourceFiles/storage/file_download.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "storage/file_download.h" +#include "data/data_document.h" #include "mainwidget.h" #include "mainwindow.h" #include "messenger.h" diff --git a/Telegram/SourceFiles/storage/file_upload.cpp b/Telegram/SourceFiles/storage/file_upload.cpp index e665a2403..ea0528eb0 100644 --- a/Telegram/SourceFiles/storage/file_upload.cpp +++ b/Telegram/SourceFiles/storage/file_upload.cpp @@ -20,6 +20,9 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "storage/file_upload.h" +#include "data/data_document.h" +#include "data/data_photo.h" + namespace Storage { namespace { diff --git a/Telegram/SourceFiles/storage/localimageloader.cpp b/Telegram/SourceFiles/storage/localimageloader.cpp index c9e34805b..a18f8fa3a 100644 --- a/Telegram/SourceFiles/storage/localimageloader.cpp +++ b/Telegram/SourceFiles/storage/localimageloader.cpp @@ -20,6 +20,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #include "storage/localimageloader.h" +#include "data/data_document.h" #include "core/file_utilities.h" #include "media/media_audio.h" #include "boxes/send_files_box.h" diff --git a/Telegram/SourceFiles/storage/localstorage.cpp b/Telegram/SourceFiles/storage/localstorage.cpp index d11e7a9d5..57e63196f 100644 --- a/Telegram/SourceFiles/storage/localstorage.cpp +++ b/Telegram/SourceFiles/storage/localstorage.cpp @@ -888,7 +888,7 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting stream >> dcIdWithShift >> flags >> ip >> port; if (!_checkStreamStatus(stream)) return false; - context.dcOptions.constructAddOne(dcIdWithShift, MTPDdcOption::Flags(flags), ip.toStdString(), port); + context.dcOptions.constructAddOne(dcIdWithShift, MTPDdcOption::Flags::from_raw(flags), ip.toStdString(), port); } break; case dbiDcOptions: { @@ -3335,7 +3335,7 @@ void _readStickerSets(FileKey &stickersKey, Stickers::Order *outOrder = nullptr, if (stickers.version > 8033) { qint32 setFlagsValue = 0; stickers.stream >> setHash >> setFlagsValue; - setFlags = MTPDstickerSet::Flags{ setFlagsValue }; + setFlags = MTPDstickerSet::Flags::from_raw(setFlagsValue); if (setFlags & MTPDstickerSet_ClientFlag::f_not_loaded__old) { setFlags &= ~MTPDstickerSet_ClientFlag::f_not_loaded__old; setFlags |= MTPDstickerSet_ClientFlag::f_not_loaded; @@ -4012,13 +4012,13 @@ uint32 _peerSize(PeerData *peer) { } else if (peer->isChat()) { ChatData *chat = peer->asChat(); - // name + count + date + version + admin + forbidden + left + inviteLink - result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(chat->inviteLink()); + // name + count + date + version + admin + old forbidden + left + inviteLink + result += Serialize::stringSize(chat->name) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint32) + Serialize::stringSize(chat->inviteLink()); } else if (peer->isChannel()) { ChannelData *channel = peer->asChannel(); - // name + access + date + version + forbidden + flags + inviteLink - result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + Serialize::stringSize(channel->inviteLink()); + // name + access + date + version + old forbidden + flags + inviteLink + result += Serialize::stringSize(channel->name) + sizeof(quint64) + sizeof(qint32) + sizeof(qint32) + sizeof(qint32) + sizeof(quint32) + Serialize::stringSize(channel->inviteLink()); } return result; } @@ -4029,26 +4029,24 @@ void _writePeer(QDataStream &stream, PeerData *peer) { if (peer->isUser()) { UserData *user = peer->asUser(); - stream << user->firstName << user->lastName << user->phone() << user->username << quint64(user->access); + stream << user->firstName << user->lastName << user->phone() << user->username << quint64(user->accessHash()); if (AppVersion >= 9012) { - stream << qint32(user->flags); + stream << qint32(user->flags()); } if (AppVersion >= 9016) { stream << (user->botInfo ? user->botInfo->inlinePlaceholder : QString()); } stream << qint32(user->onlineTill) << qint32(user->contact) << qint32(user->botInfo ? user->botInfo->version : -1); } else if (peer->isChat()) { - ChatData *chat = peer->asChat(); - - qint32 flagsData = (AppVersion >= 9012) ? chat->flags : (chat->haveLeft() ? 1 : 0); + auto chat = peer->asChat(); stream << chat->name << qint32(chat->count) << qint32(chat->date) << qint32(chat->version) << qint32(chat->creator); - stream << qint32(chat->isForbidden() ? 1 : 0) << qint32(flagsData) << chat->inviteLink(); + stream << qint32(0) << quint32(chat->flags()) << chat->inviteLink(); } else if (peer->isChannel()) { - ChannelData *channel = peer->asChannel(); + auto channel = peer->asChannel(); stream << channel->name << quint64(channel->access) << qint32(channel->date) << qint32(channel->version); - stream << qint32(channel->isForbidden() ? 1 : 0) << qint32(channel->flags) << channel->inviteLink(); + stream << qint32(0) << quint32(channel->flags()) << channel->inviteLink(); } } @@ -4086,8 +4084,8 @@ PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { user->setPhone(phone); user->setName(first, last, pname, username); - user->access = access; - user->flags = MTPDuser::Flags(flags); + user->setFlags(MTPDuser::Flags::from_raw(flags)); + user->setAccessHash(access); user->onlineTill = onlineTill; user->contact = contact; user->setBotInfoVersion(botInfoVersion); @@ -4099,8 +4097,8 @@ PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { user->input = MTP_inputPeerSelf(); user->inputUser = MTP_inputUserSelf(); } else { - user->input = MTP_inputPeerUser(MTP_int(peerToUser(user->id)), MTP_long(user->isInaccessible() ? 0 : user->access)); - user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long(user->isInaccessible() ? 0 : user->access)); + user->input = MTP_inputPeerUser(MTP_int(peerToUser(user->id)), MTP_long(user->accessHash())); + user->inputUser = MTP_inputUser(MTP_int(peerToUser(user->id)), MTP_long(user->accessHash())); } user->setUserpic(photoLoc.isNull() ? ImagePtr() : ImagePtr(photoLoc)); @@ -4109,14 +4107,20 @@ PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { ChatData *chat = result->asChat(); QString name, inviteLink; - qint32 count, date, version, creator, forbidden, flagsData, flags; - from.stream >> name >> count >> date >> version >> creator >> forbidden >> flagsData >> inviteLink; + qint32 count, date, version, creator, oldForbidden; + quint32 flagsData, flags; + from.stream >> name >> count >> date >> version >> creator >> oldForbidden >> flagsData >> inviteLink; if (from.version >= 9012) { flags = flagsData; } else { // flagsData was haveLeft - flags = (flagsData == 1) ? MTPDchat::Flags(MTPDchat::Flag::f_left) : MTPDchat::Flags(0); + flags = (flagsData == 1) + ? MTPDchat::Flags(MTPDchat::Flag::f_left) + : MTPDchat::Flags(0); + } + if (oldForbidden) { + flags |= quint32(MTPDchat_ClientFlag::f_forbidden); } if (!wasLoaded) { chat->setName(name); @@ -4124,8 +4128,7 @@ PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { chat->date = date; chat->version = version; chat->creator = creator; - chat->setIsForbidden(forbidden == 1); - chat->flags = MTPDchat::Flags(flags); + chat->setFlags(MTPDchat::Flags::from_raw(flags)); chat->setInviteLink(inviteLink); chat->input = MTP_inputPeerChat(MTP_int(peerToChat(chat->id))); @@ -4138,16 +4141,18 @@ PeerData *_readPeer(FileReadDescriptor &from, int32 fileVersion = 0) { QString name, inviteLink; quint64 access; - qint32 date, version, forbidden, flags; - from.stream >> name >> access >> date >> version >> forbidden >> flags >> inviteLink; - + qint32 date, version, oldForbidden; + quint32 flags; + from.stream >> name >> access >> date >> version >> oldForbidden >> flags >> inviteLink; + if (oldForbidden) { + flags |= quint32(MTPDchannel_ClientFlag::f_forbidden); + } if (!wasLoaded) { channel->setName(name, QString()); channel->access = access; channel->date = date; channel->version = version; - channel->setIsForbidden(forbidden == 1); - channel->flags = MTPDchannel::Flags(flags); + channel->setFlags(MTPDchannel::Flags::from_raw(flags)); channel->setInviteLink(inviteLink); channel->input = MTP_inputPeerChannel(MTP_int(peerToChannel(channel->id)), MTP_long(access)); diff --git a/Telegram/SourceFiles/storage/serialize_document.h b/Telegram/SourceFiles/storage/serialize_document.h index 8de762bd0..78b6e056b 100644 --- a/Telegram/SourceFiles/storage/serialize_document.h +++ b/Telegram/SourceFiles/storage/serialize_document.h @@ -20,13 +20,12 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org */ #pragma once -#include "structs.h" +#include "data/data_document.h" namespace Serialize { class Document { public: - struct StickerSetInfo { StickerSetInfo(uint64 setId, uint64 accessHash, QString shortName) : setId(setId) diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp deleted file mode 100644 index f025a5364..000000000 --- a/Telegram/SourceFiles/structs.cpp +++ /dev/null @@ -1,2199 +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 "structs.h" - -#include "lang/lang_keys.h" -#include "inline_bots/inline_bot_layout_item.h" -#include "observer_peer.h" -#include "mainwidget.h" -#include "application.h" -#include "storage/file_upload.h" -#include "mainwindow.h" -#include "core/file_utilities.h" -#include "apiwrap.h" -#include "boxes/confirm_box.h" -#include "media/media_audio.h" -#include "storage/localstorage.h" -#include "history/history_media_types.h" -#include "styles/style_history.h" -#include "window/themes/window_theme.h" -#include "auth_session.h" -#include "messenger.h" -#include "storage/file_download.h" - -namespace { - -constexpr auto kUpdateFullPeerTimeout = TimeMs(5000); // Not more than once in 5 seconds. - -int peerColorIndex(const PeerId &peer) { - auto myId = Auth().userId(); - auto peerId = peerToBareInt(peer); - auto both = (QByteArray::number(peerId) + QByteArray::number(myId)).mid(0, 15); - uchar md5[16]; - hashMd5(both.constData(), both.size(), md5); - return (md5[peerId & 0x0F] & (peerIsUser(peer) ? 0x07 : 0x03)); -} - -ImagePtr generateUserpicImage(const style::icon &icon) { - auto data = QImage(icon.size() * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); - data.setDevicePixelRatio(cRetinaFactor()); - { - Painter p(&data); - icon.paint(p, 0, 0, icon.width()); - } - return ImagePtr(App::pixmapFromImageInPlace(std::move(data)), "PNG"); -} - -} // namespace - -style::color peerUserpicColor(int index) { - static style::color peerColors[kUserColorsCount] = { - st::historyPeer1UserpicBg, - st::historyPeer2UserpicBg, - st::historyPeer3UserpicBg, - st::historyPeer4UserpicBg, - st::historyPeer5UserpicBg, - st::historyPeer6UserpicBg, - st::historyPeer7UserpicBg, - st::historyPeer8UserpicBg, - }; - return peerColors[index]; -} - -class EmptyUserpic::Impl { -public: - Impl(int index, const QString &name) : _color(peerUserpicColor(index)) { - fillString(name); - } - - void paint(Painter &p, int x, int y, int size); - void paintRounded(Painter &p, int x, int y, int size); - void paintSquare(Painter &p, int x, int y, int size); - StorageKey uniqueKey() const; - -private: - template - void paint(Painter &p, int x, int y, int size, PaintBackground paintBackground); - - void fillString(const QString &name); - - style::color _color; - QString _string; - -}; - -template -void EmptyUserpic::Impl::paint(Painter &p, int x, int y, int size, PaintBackground paintBackground) { - auto fontsize = (size * 13) / 33; - auto font = st::historyPeerUserpicFont->f; - font.setPixelSize(fontsize); - - PainterHighQualityEnabler hq(p); - p.setBrush(_color); - p.setPen(Qt::NoPen); - paintBackground(); - - p.setFont(font); - p.setBrush(Qt::NoBrush); - p.setPen(st::historyPeerUserpicFg); - p.drawText(QRect(x, y, size, size), _string, QTextOption(style::al_center)); -} - -void EmptyUserpic::Impl::paint(Painter &p, int x, int y, int size) { - paint(p, x, y, size, [&p, x, y, size] { - p.drawEllipse(x, y, size, size); - }); -} - -void EmptyUserpic::Impl::paintRounded(Painter &p, int x, int y, int size) { - paint(p, x, y, size, [&p, x, y, size] { - p.drawRoundedRect(x, y, size, size, st::buttonRadius, st::buttonRadius); - }); -} - -void EmptyUserpic::Impl::paintSquare(Painter &p, int x, int y, int size) { - paint(p, x, y, size, [&p, x, y, size] { - p.fillRect(x, y, size, size, p.brush()); - }); -} - -StorageKey EmptyUserpic::Impl::uniqueKey() const { - auto first = 0xFFFFFFFF00000000ULL | anim::getPremultiplied(_color->c); - auto second = uint64(0); - memcpy(&second, _string.constData(), qMin(sizeof(second), _string.size() * sizeof(QChar))); - return StorageKey(first, second); -} - -void EmptyUserpic::Impl::fillString(const QString &name) { - QList letters; - QList levels; - auto level = 0; - auto letterFound = false; - auto ch = name.constData(), end = ch + name.size(); - while (ch != end) { - auto emojiLength = 0; - if (auto emoji = Ui::Emoji::Find(ch, end, &emojiLength)) { - ch += emojiLength; - } else if (ch->isHighSurrogate()) { - ++ch; - if (ch != end && ch->isLowSurrogate()) { - ++ch; - } - } else if (!letterFound && ch->isLetterOrNumber()) { - letterFound = true; - if (ch + 1 != end && chIsDiac(*(ch + 1))) { - letters.push_back(QString(ch, 2)); - levels.push_back(level); - ++ch; - } else { - letters.push_back(QString(ch, 1)); - levels.push_back(level); - } - ++ch; - } else { - if (*ch == ' ') { - level = 0; - letterFound = false; - } else if (letterFound && *ch == '-') { - level = 1; - letterFound = true; - } - ++ch; - } - } - - // We prefer the second letter to be after ' ', but it can also be after '-'. - _string = QString(); - if (!letters.isEmpty()) { - _string += letters.front(); - auto bestIndex = 0; - auto bestLevel = 2; - for (auto i = letters.size(); i != 1;) { - if (levels[--i] < bestLevel) { - bestIndex = i; - bestLevel = levels[i]; - } - } - if (bestIndex > 0) { - _string += letters[bestIndex]; - } - } - _string = _string.toUpper(); -} - -EmptyUserpic::EmptyUserpic() = default; - -EmptyUserpic::EmptyUserpic(int index, const QString &name) : _impl(std::make_unique(index, name)) { -} - -void EmptyUserpic::set(int index, const QString &name) { - _impl = std::make_unique(index, name); -} - -void EmptyUserpic::clear() { - _impl.reset(); -} - -void EmptyUserpic::paint(Painter &p, int x, int y, int outerWidth, int size) const { - Expects(_impl != nullptr); - _impl->paint(p, rtl() ? (outerWidth - x - size) : x, y, size); -} - -void EmptyUserpic::paintRounded(Painter &p, int x, int y, int outerWidth, int size) const { - Expects(_impl != nullptr); - _impl->paintRounded(p, rtl() ? (outerWidth - x - size) : x, y, size); -} - -void EmptyUserpic::paintSquare(Painter &p, int x, int y, int outerWidth, int size) const { - Expects(_impl != nullptr); - _impl->paintSquare(p, rtl() ? (outerWidth - x - size) : x, y, size); -} - -StorageKey EmptyUserpic::uniqueKey() const { - Expects(_impl != nullptr); - return _impl->uniqueKey(); -} - -QPixmap EmptyUserpic::generate(int size) { - auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); - result.setDevicePixelRatio(cRetinaFactor()); - result.fill(Qt::transparent); - { - Painter p(&result); - paint(p, 0, 0, size, size); - } - return App::pixmapFromImageInPlace(std::move(result)); -} - -EmptyUserpic::~EmptyUserpic() = default; - -using UpdateFlag = Notify::PeerUpdate::Flag; - -NotifySettings globalNotifyAll, globalNotifyUsers, globalNotifyChats; -NotifySettingsPtr globalNotifyAllPtr = UnknownNotifySettings, globalNotifyUsersPtr = UnknownNotifySettings, globalNotifyChatsPtr = UnknownNotifySettings; - -PeerClickHandler::PeerClickHandler(not_null peer) : _peer(peer) { -} - -void PeerClickHandler::onClick(Qt::MouseButton button) const { - if (button == Qt::LeftButton && App::main()) { - if (_peer && _peer->isChannel() && App::main()->historyPeer() != _peer) { - if (!_peer->asChannel()->isPublic() && !_peer->asChannel()->amIn()) { - Ui::show(Box(lang((_peer->isMegagroup()) ? lng_group_not_accessible : lng_channel_not_accessible))); - } else { - Ui::showPeerHistory(_peer, ShowAtUnreadMsgId, Ui::ShowWay::Forward); - } - } else { - Ui::showPeerProfile(_peer); - } - } -} - -PeerData::PeerData(const PeerId &id) : id(id), _colorIndex(peerColorIndex(id)) { - nameText.setText(st::msgNameStyle, QString(), _textNameOptions); - _userpicEmpty.set(_colorIndex, QString()); -} - -void PeerData::updateNameDelayed(const QString &newName, const QString &newNameOrPhone, const QString &newUsername) { - if (name == newName) { - if (isUser()) { - if (asUser()->nameOrPhone == newNameOrPhone && asUser()->username == newUsername) { - return; - } - } else if (isChannel()) { - if (asChannel()->username == newUsername) { - return; - } - } else if (isChat()) { - return; - } - } - - ++nameVersion; - name = newName; - nameText.setText(st::msgNameStyle, name, _textNameOptions); - if (!_userpic) { - _userpicEmpty.set(_colorIndex, name); - } - - Notify::PeerUpdate update(this); - update.flags |= UpdateFlag::NameChanged; - update.oldNames = names; - update.oldNameFirstChars = chars; - - if (isUser()) { - if (asUser()->username != newUsername) { - asUser()->username = newUsername; - update.flags |= UpdateFlag::UsernameChanged; - } - asUser()->setNameOrPhone(newNameOrPhone); - } else if (isChannel()) { - if (asChannel()->username != newUsername) { - asChannel()->username = newUsername; - if (newUsername.isEmpty()) { - asChannel()->flags &= ~MTPDchannel::Flag::f_username; - } else { - asChannel()->flags |= MTPDchannel::Flag::f_username; - } - update.flags |= UpdateFlag::UsernameChanged; - } - } - fillNames(); - Notify::PeerUpdated().notify(update, true); -} - -ClickHandlerPtr PeerData::createOpenLink() { - return MakeShared(this); -} - -void PeerData::setUserpic(ImagePtr userpic) { - _userpic = userpic; - if (!_userpic || !_userpic->loaded()) { - _userpicEmpty.set(_colorIndex, name); - } else { - _userpicEmpty.clear(); - } -} - -ImagePtr PeerData::currentUserpic() const { - if (_userpic) { - _userpic->load(); - if (_userpic->loaded()) { - _userpicEmpty.clear(); - return _userpic; - } - } - return ImagePtr(); -} - -void PeerData::paintUserpic(Painter &p, int x, int y, int size) const { - if (auto userpic = currentUserpic()) { - p.drawPixmap(x, y, userpic->pixCircled(size, size)); - } else { - _userpicEmpty.paint(p, x, y, x + size + x, size); - } -} - -void PeerData::paintUserpicRounded(Painter &p, int x, int y, int size) const { - if (auto userpic = currentUserpic()) { - p.drawPixmap(x, y, userpic->pixRounded(size, size, ImageRoundRadius::Small)); - } else { - _userpicEmpty.paintRounded(p, x, y, x + size + x, size); - } -} - -void PeerData::paintUserpicSquare(Painter &p, int x, int y, int size) const { - if (auto userpic = currentUserpic()) { - p.drawPixmap(x, y, userpic->pix(size, size)); - } else { - _userpicEmpty.paintSquare(p, x, y, x + size + x, size); - } -} - -StorageKey PeerData::userpicUniqueKey() const { - if (photoLoc.isNull() || !_userpic || !_userpic->loaded()) { - return _userpicEmpty.uniqueKey(); - } - return storageKey(photoLoc); -} - -void PeerData::saveUserpic(const QString &path, int size) const { - genUserpic(size).save(path, "PNG"); -} - -void PeerData::saveUserpicRounded(const QString &path, int size) const { - genUserpicRounded(size).save(path, "PNG"); -} - -QPixmap PeerData::genUserpic(int size) const { - if (auto userpic = currentUserpic()) { - return userpic->pixCircled(size, size); - } - auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); - result.setDevicePixelRatio(cRetinaFactor()); - result.fill(Qt::transparent); - { - Painter p(&result); - paintUserpic(p, 0, 0, size); - } - return App::pixmapFromImageInPlace(std::move(result)); -} - -QPixmap PeerData::genUserpicRounded(int size) const { - if (auto userpic = currentUserpic()) { - return userpic->pixRounded(size, size, ImageRoundRadius::Small); - } - auto result = QImage(QSize(size, size) * cIntRetinaFactor(), QImage::Format_ARGB32_Premultiplied); - result.setDevicePixelRatio(cRetinaFactor()); - result.fill(Qt::transparent); - { - Painter p(&result); - paintUserpicRounded(p, 0, 0, size); - } - return App::pixmapFromImageInPlace(std::move(result)); -} - -const Text &BotCommand::descriptionText() const { - if (_descriptionText.isEmpty() && !_description.isEmpty()) { - _descriptionText.setText(st::defaultTextStyle, _description, _textNameOptions); - } - return _descriptionText; -} - -bool UserData::canShareThisContact() const { - return canShareThisContactFast() || !App::phoneFromSharedContact(peerToUser(id)).isEmpty(); -} - -void UserData::setPhoto(const MTPUserProfilePhoto &p) { // see Local::readPeer as well - PhotoId newPhotoId = photoId; - ImagePtr newPhoto = _userpic; - StorageImageLocation newPhotoLoc = photoLoc; - switch (p.type()) { - case mtpc_userProfilePhoto: { - const auto &d(p.c_userProfilePhoto()); - newPhotoId = d.vphoto_id.v; - newPhotoLoc = App::imageLocation(160, 160, d.vphoto_small); - newPhoto = newPhotoLoc.isNull() ? ImagePtr() : ImagePtr(newPhotoLoc); - //App::feedPhoto(App::photoFromUserPhoto(peerToUser(id), MTP_int(unixtime()), p)); - } break; - default: { - newPhotoId = 0; - if (id == ServiceUserId) { - if (!_userpic) { - newPhoto = ImagePtr(App::pixmapFromImageInPlace(Messenger::Instance().logoNoMargin().scaledToWidth(160, Qt::SmoothTransformation)), "PNG"); - } - } else { - newPhoto = ImagePtr(); - } - newPhotoLoc = StorageImageLocation(); - } break; - } - if (newPhotoId != photoId || newPhoto.v() != _userpic.v() || newPhotoLoc != photoLoc) { - photoId = newPhotoId; - setUserpic(newPhoto); - photoLoc = newPhotoLoc; - Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged); - } -} - -void PeerData::fillNames() { - names.clear(); - chars.clear(); - auto toIndex = TextUtilities::RemoveAccents(name); - if (cRussianLetters().match(toIndex).hasMatch()) { - toIndex += ' ' + translitRusEng(toIndex); - } - if (isUser()) { - if (!asUser()->nameOrPhone.isEmpty() && asUser()->nameOrPhone != name) toIndex += ' ' + TextUtilities::RemoveAccents(asUser()->nameOrPhone); - if (!asUser()->username.isEmpty()) toIndex += ' ' + TextUtilities::RemoveAccents(asUser()->username); - } else if (isChannel()) { - if (!asChannel()->username.isEmpty()) toIndex += ' ' + TextUtilities::RemoveAccents(asChannel()->username); - } - toIndex += ' ' + rusKeyboardLayoutSwitch(toIndex); - - auto namesList = TextUtilities::PrepareSearchWords(toIndex); - for (auto &name : namesList) { - names.insert(name); - chars.insert(name[0]); - } -} - -bool UserData::setAbout(const QString &newAbout) { - if (_about == newAbout) { - return false; - } - _about = newAbout; - Notify::peerUpdatedDelayed(this, UpdateFlag::AboutChanged); - return true; -} - -void UserData::setRestrictionReason(const QString &text) { - if (_restrictionReason != text) { - _restrictionReason = text; - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::RestrictionReasonChanged); - } -} - -void UserData::setCommonChatsCount(int count) { - if (_commonChatsCount != count) { - _commonChatsCount = count; - Notify::peerUpdatedDelayed(this, UpdateFlag::UserCommonChatsChanged); - } -} - -void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) { - bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty(); - - QString newFullName; - if (changeName && newFirstName.trimmed().isEmpty()) { - firstName = newLastName; - lastName = QString(); - newFullName = firstName; - } else { - if (changeName) { - firstName = newFirstName; - lastName = newLastName; - } - newFullName = lastName.isEmpty() ? firstName : lng_full_name(lt_first_name, firstName, lt_last_name, lastName); - } - updateNameDelayed(newFullName, newPhoneName, newUsername); -} - -void UserData::setPhone(const QString &newPhone) { - _phone = newPhone; -} - -void UserData::setBotInfoVersion(int version) { - if (version < 0) { - if (botInfo) { - if (!botInfo->commands.isEmpty()) { - botInfo->commands.clear(); - Notify::botCommandsChanged(this); - } - botInfo = nullptr; - Notify::userIsBotChanged(this); - } - } else if (!botInfo) { - botInfo = std::make_unique(); - botInfo->version = version; - Notify::userIsBotChanged(this); - } else if (botInfo->version < version) { - if (!botInfo->commands.isEmpty()) { - botInfo->commands.clear(); - Notify::botCommandsChanged(this); - } - botInfo->description.clear(); - botInfo->version = version; - botInfo->inited = false; - } -} - -void UserData::setBotInfo(const MTPBotInfo &info) { - switch (info.type()) { - case mtpc_botInfo: { - const auto &d(info.c_botInfo()); - if (peerFromUser(d.vuser_id.v) != id || !botInfo) return; - - QString desc = qs(d.vdescription); - if (botInfo->description != desc) { - botInfo->description = desc; - botInfo->text = Text(st::msgMinWidth); - } - - auto &v = d.vcommands.v; - botInfo->commands.reserve(v.size()); - auto changedCommands = false; - int32 j = 0; - for (int32 i = 0, l = v.size(); i < l; ++i) { - if (v.at(i).type() != mtpc_botCommand) continue; - - QString cmd = qs(v.at(i).c_botCommand().vcommand), desc = qs(v.at(i).c_botCommand().vdescription); - if (botInfo->commands.size() <= j) { - botInfo->commands.push_back(BotCommand(cmd, desc)); - changedCommands = true; - } else { - if (botInfo->commands[j].command != cmd) { - botInfo->commands[j].command = cmd; - changedCommands = true; - } - if (botInfo->commands[j].setDescription(desc)) { - changedCommands = true; - } - } - ++j; - } - while (j < botInfo->commands.size()) { - botInfo->commands.pop_back(); - changedCommands = true; - } - - botInfo->inited = true; - - if (changedCommands) { - Notify::botCommandsChanged(this); - } - } break; - } -} - -void UserData::setNameOrPhone(const QString &newNameOrPhone) { - if (nameOrPhone != newNameOrPhone) { - nameOrPhone = newNameOrPhone; - phoneText.setText(st::msgNameStyle, nameOrPhone, _textNameOptions); - } -} - -void UserData::madeAction(TimeId when) { - if (botInfo || isServiceUser(id) || when <= 0) return; - - if (onlineTill <= 0 && -onlineTill < when) { - onlineTill = -when - SetOnlineAfterActivity; - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::UserOnlineChanged); - } else if (onlineTill > 0 && onlineTill < when + 1) { - onlineTill = when + SetOnlineAfterActivity; - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::UserOnlineChanged); - } -} - -void UserData::setBlockStatus(BlockStatus blockStatus) { - if (blockStatus != _blockStatus) { - _blockStatus = blockStatus; - Notify::peerUpdatedDelayed(this, UpdateFlag::UserIsBlocked); - } -} - -void UserData::setCallsStatus(CallsStatus callsStatus) { - if (callsStatus != _callsStatus) { - _callsStatus = callsStatus; - Notify::peerUpdatedDelayed(this, UpdateFlag::UserHasCalls); - } -} - -bool UserData::hasCalls() const { - return (callsStatus() != CallsStatus::Disabled) && (callsStatus() != CallsStatus::Unknown); -} - -void ChatData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see Local::readPeer as well - PhotoId newPhotoId = photoId; - ImagePtr newPhoto = _userpic; - StorageImageLocation newPhotoLoc = photoLoc; - switch (p.type()) { - case mtpc_chatPhoto: { - const auto &d(p.c_chatPhoto()); - if (phId != UnknownPeerPhotoId) { - newPhotoId = phId; - } - newPhotoLoc = App::imageLocation(160, 160, d.vphoto_small); - newPhoto = newPhotoLoc.isNull() ? ImagePtr() : ImagePtr(newPhotoLoc); -// photoFull = newPhoto ? ImagePtr(640, 640, d.vphoto_big, ImagePtr()) : ImagePtr(); - } break; - default: { - newPhotoId = 0; - newPhotoLoc = StorageImageLocation(); - newPhoto = ImagePtr(); -// photoFull = ImagePtr(); - } break; - } - if (newPhotoId != photoId || newPhoto.v() != _userpic.v() || newPhotoLoc != photoLoc) { - photoId = newPhotoId; - setUserpic(newPhoto); - photoLoc = newPhotoLoc; - Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged); - } -} - -void ChatData::setName(const QString &newName) { - updateNameDelayed(newName.isEmpty() ? name : newName, QString(), QString()); -} - -void ChatData::invalidateParticipants() { - auto wasCanEdit = canEdit(); - participants.clear(); - admins.clear(); - flags &= ~MTPDchat::Flag::f_admin; - invitedByMe.clear(); - botStatus = 0; - if (wasCanEdit != canEdit()) { - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::ChatCanEdit); - } - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::MembersChanged | Notify::PeerUpdate::Flag::AdminsChanged); -} - -void ChatData::setInviteLink(const QString &newInviteLink) { - if (newInviteLink != _inviteLink) { - _inviteLink = newInviteLink; - Notify::peerUpdatedDelayed(this, UpdateFlag::InviteLinkChanged); - } -} - -void ChannelData::setPhoto(const MTPChatPhoto &p, const PhotoId &phId) { // see Local::readPeer as well - PhotoId newPhotoId = photoId; - ImagePtr newPhoto = _userpic; - StorageImageLocation newPhotoLoc = photoLoc; - switch (p.type()) { - case mtpc_chatPhoto: { - const auto &d(p.c_chatPhoto()); - if (phId != UnknownPeerPhotoId) { - newPhotoId = phId; - } - newPhotoLoc = App::imageLocation(160, 160, d.vphoto_small); - newPhoto = newPhotoLoc.isNull() ? ImagePtr() : ImagePtr(newPhotoLoc); -// photoFull = newPhoto ? ImagePtr(640, 640, d.vphoto_big, newPhoto) : ImagePtr(); - } break; - default: { - newPhotoId = 0; - newPhotoLoc = StorageImageLocation(); - newPhoto = ImagePtr(); -// photoFull = ImagePtr(); - } break; - } - if (newPhotoId != photoId || newPhoto.v() != _userpic.v() || newPhotoLoc != photoLoc) { - photoId = newPhotoId; - setUserpic(newPhoto); - photoLoc = newPhotoLoc; - Notify::peerUpdatedDelayed(this, UpdateFlag::PhotoChanged); - } -} - -void ChannelData::setName(const QString &newName, const QString &newUsername) { - updateNameDelayed(newName.isEmpty() ? name : newName, QString(), newUsername); -} - -void PeerData::updateFull() { - if (!_lastFullUpdate || getms(true) > _lastFullUpdate + kUpdateFullPeerTimeout) { - updateFullForced(); - } -} - -void PeerData::updateFullForced() { - Auth().api().requestFullPeer(this); - if (auto channel = asChannel()) { - if (!channel->amCreator() && !channel->inviter) { - Auth().api().requestSelfParticipant(channel); - } - } -} - -void PeerData::fullUpdated() { - _lastFullUpdate = getms(true); -} - -bool ChannelData::setAbout(const QString &newAbout) { - if (_about == newAbout) { - return false; - } - _about = newAbout; - Notify::peerUpdatedDelayed(this, UpdateFlag::AboutChanged); - return true; -} - -void ChannelData::setInviteLink(const QString &newInviteLink) { - if (newInviteLink != _inviteLink) { - _inviteLink = newInviteLink; - Notify::peerUpdatedDelayed(this, UpdateFlag::InviteLinkChanged); - } -} - -void ChannelData::setMembersCount(int newMembersCount) { - if (_membersCount != newMembersCount) { - if (isMegagroup() && !mgInfo->lastParticipants.isEmpty()) { - mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; - mgInfo->lastParticipantsCount = membersCount(); - } - _membersCount = newMembersCount; - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::MembersChanged); - } -} - -void ChannelData::setAdminsCount(int newAdminsCount) { - if (_adminsCount != newAdminsCount) { - _adminsCount = newAdminsCount; - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::AdminsChanged); - } -} - -void ChannelData::setRestrictedCount(int newRestrictedCount) { - if (_restrictedCount != newRestrictedCount) { - _restrictedCount = newRestrictedCount; - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::BannedUsersChanged); - } -} - -void ChannelData::setKickedCount(int newKickedCount) { - if (_kickedCount != newKickedCount) { - _kickedCount = newKickedCount; - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::BannedUsersChanged); - } -} - -MTPChannelBannedRights ChannelData::KickedRestrictedRights() { - using Flag = MTPDchannelBannedRights::Flag; - auto flags = Flag::f_view_messages | Flag::f_send_messages | Flag::f_send_media | Flag::f_embed_links | Flag::f_send_stickers | Flag::f_send_gifs | Flag::f_send_games | Flag::f_send_inline; - return MTP_channelBannedRights(MTP_flags(flags), MTP_int(std::numeric_limits::max())); -} - -void ChannelData::applyEditAdmin(not_null user, const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights) { - auto flags = Notify::PeerUpdate::Flag::AdminsChanged | Notify::PeerUpdate::Flag::None; - if (mgInfo) { - if (!mgInfo->lastParticipants.contains(user)) { // If rights are empty - still add participant? TODO check - mgInfo->lastParticipants.push_front(user); - setMembersCount(membersCount() + 1); - if (user->botInfo && !mgInfo->bots.contains(user)) { - mgInfo->bots.insert(user); - if (mgInfo->botStatus != 0 && mgInfo->botStatus < 2) { - mgInfo->botStatus = 2; - } - } - } - if (mgInfo->lastRestricted.contains(user)) { // If rights are empty - still remove restrictions? TODO check - mgInfo->lastRestricted.remove(user); - if (restrictedCount() > 0) { - setRestrictedCount(restrictedCount() - 1); - } - } - auto it = mgInfo->lastAdmins.find(user); - if (newRights.c_channelAdminRights().vflags.v != 0) { - auto lastAdmin = MegagroupInfo::Admin { newRights }; - lastAdmin.canEdit = true; - if (it == mgInfo->lastAdmins.cend()) { - mgInfo->lastAdmins.insert(user, lastAdmin); - setAdminsCount(adminsCount() + 1); - } else { - it.value() = lastAdmin; - } - } else { - if (it != mgInfo->lastAdmins.cend()) { - mgInfo->lastAdmins.erase(it); - if (adminsCount() > 0) { - setAdminsCount(adminsCount() - 1); - } - } - } - } - if (oldRights.c_channelAdminRights().vflags.v && !newRights.c_channelAdminRights().vflags.v) { - // We removed an admin. - if (adminsCount() > 1) { - setAdminsCount(adminsCount() - 1); - } - if (!isMegagroup() && user->botInfo && membersCount() > 1) { - // Removing bot admin removes it from channel. - setMembersCount(membersCount() - 1); - } - } else if (!oldRights.c_channelAdminRights().vflags.v && newRights.c_channelAdminRights().vflags.v) { - // We added an admin. - setAdminsCount(adminsCount() + 1); - updateFullForced(); - } - Notify::peerUpdatedDelayed(this, flags); -} - -void ChannelData::applyEditBanned(not_null user, const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights) { - auto flags = Notify::PeerUpdate::Flag::BannedUsersChanged | Notify::PeerUpdate::Flag::None; - if (mgInfo) { - if (mgInfo->lastAdmins.contains(user)) { // If rights are empty - still remove admin? TODO check - mgInfo->lastAdmins.remove(user); - if (adminsCount() > 1) { - setAdminsCount(adminsCount() - 1); - } else { - flags |= Notify::PeerUpdate::Flag::AdminsChanged; - } - } - auto isKicked = (newRights.c_channelBannedRights().vflags.v & MTPDchannelBannedRights::Flag::f_view_messages); - auto isRestricted = !isKicked && (newRights.c_channelBannedRights().vflags.v != 0); - auto it = mgInfo->lastRestricted.find(user); - if (isRestricted) { - if (it == mgInfo->lastRestricted.cend()) { - mgInfo->lastRestricted.insert(user, MegagroupInfo::Restricted { newRights }); - setRestrictedCount(restrictedCount() + 1); - } else { - it->rights = newRights; - } - } else { - if (it != mgInfo->lastRestricted.cend()) { - mgInfo->lastRestricted.erase(it); - if (restrictedCount() > 0) { - setRestrictedCount(restrictedCount() - 1); - } - } - if (isKicked) { - auto i = mgInfo->lastParticipants.indexOf(user); - if (i >= 0) { - mgInfo->lastParticipants.removeAt(i); - } - if (membersCount() > 1) { - setMembersCount(membersCount() - 1); - } else { - mgInfo->lastParticipantsStatus |= MegagroupInfo::LastParticipantsCountOutdated; - mgInfo->lastParticipantsCount = 0; - } - setKickedCount(kickedCount() + 1); - if (mgInfo->bots.contains(user)) { - mgInfo->bots.remove(user); - if (mgInfo->bots.isEmpty() && mgInfo->botStatus > 0) { - mgInfo->botStatus = -1; - } - } - flags |= Notify::PeerUpdate::Flag::MembersChanged; - } - } - } - Notify::peerUpdatedDelayed(this, flags); -} - -void ChannelData::flagsUpdated() { - if (isMegagroup()) { - if (!mgInfo) { - mgInfo = std::make_unique(); - } - } else if (mgInfo) { - mgInfo = nullptr; - } -} - -void ChannelData::setRestrictionReason(const QString &text) { - if (_restrictionReason != text) { - _restrictionReason = text; - Notify::peerUpdatedDelayed(this, Notify::PeerUpdate::Flag::RestrictionReasonChanged); - } -} - -bool ChannelData::canNotEditLastAdmin(not_null user) const { - if (mgInfo) { - auto i = mgInfo->lastAdmins.constFind(user); - if (i != mgInfo->lastAdmins.cend()) { - return !i->canEdit; - } - return (user == mgInfo->creator); - } - return false; -} - -bool ChannelData::canEditAdmin(not_null user) const { - if (user->isSelf()) { - return false; - } else if (amCreator()) { - return true; - } else if (canNotEditLastAdmin(user)) { - return false; - } - return adminRights().is_add_admins(); -} - -bool ChannelData::canRestrictUser(not_null user) const { - if (user->isSelf()) { - return false; - } else if (amCreator()) { - return true; - } else if (canNotEditLastAdmin(user)) { - return false; - } - return adminRights().is_ban_users(); -} - -void ChannelData::setAdminRights(const MTPChannelAdminRights &rights) { - if (rights.c_channelAdminRights().vflags.v == _adminRights.c_channelAdminRights().vflags.v) { - return; - } - _adminRights = rights; - if (isMegagroup()) { - if (hasAdminRights()) { - if (!amCreator()) { - auto me = MegagroupInfo::Admin { _adminRights }; - me.canEdit = false; - mgInfo->lastAdmins.insert(App::self(), me); - } - mgInfo->lastRestricted.remove(App::self()); - } else { - mgInfo->lastAdmins.remove(App::self()); - } - } - Notify::peerUpdatedDelayed(this, UpdateFlag::ChannelRightsChanged | UpdateFlag::AdminsChanged | UpdateFlag::BannedUsersChanged); -} - -void ChannelData::setRestrictedRights(const MTPChannelBannedRights &rights) { - if (rights.c_channelBannedRights().vflags.v == _restrictedRights.c_channelBannedRights().vflags.v - && rights.c_channelBannedRights().vuntil_date.v == _restrictedRights.c_channelBannedRights().vuntil_date.v) { - return; - } - _restrictedRights = rights; - if (isMegagroup()) { - if (hasRestrictedRights()) { - if (!amCreator()) { - auto me = MegagroupInfo::Restricted { _restrictedRights }; - mgInfo->lastRestricted.insert(App::self(), me); - } - mgInfo->lastAdmins.remove(App::self()); - } else { - mgInfo->lastRestricted.remove(App::self()); - } - } - Notify::peerUpdatedDelayed(this, UpdateFlag::ChannelRightsChanged | UpdateFlag::AdminsChanged | UpdateFlag::BannedUsersChanged); -} - -uint64 PtsWaiter::ptsKey(PtsSkippedQueue queue, int32 pts) { - return _queue.insert(uint64(uint32(pts)) << 32 | (++_skippedKey), queue).key(); -} - -void PtsWaiter::setWaitingForSkipped(ChannelData *channel, int32 ms) { - if (ms >= 0) { - if (App::main()) { - App::main()->ptsWaiterStartTimerFor(channel, ms); - } - _waitingForSkipped = true; - } else { - _waitingForSkipped = false; - checkForWaiting(channel); - } -} - -void PtsWaiter::setWaitingForShortPoll(ChannelData *channel, int32 ms) { - if (ms >= 0) { - if (App::main()) { - App::main()->ptsWaiterStartTimerFor(channel, ms); - } - _waitingForShortPoll = true; - } else { - _waitingForShortPoll = false; - checkForWaiting(channel); - } -} - -void PtsWaiter::checkForWaiting(ChannelData *channel) { - if (!_waitingForSkipped && !_waitingForShortPoll && App::main()) { - App::main()->ptsWaiterStartTimerFor(channel, -1); - } -} - -void PtsWaiter::applySkippedUpdates(ChannelData *channel) { - if (!_waitingForSkipped) return; - - setWaitingForSkipped(channel, -1); - - if (_queue.isEmpty()) return; - - ++_applySkippedLevel; - for (auto i = _queue.cbegin(), e = _queue.cend(); i != e; ++i) { - switch (i.value()) { - case SkippedUpdate: Auth().api().applyUpdateNoPtsCheck(_updateQueue.value(i.key())); break; - case SkippedUpdates: Auth().api().applyUpdatesNoPtsCheck(_updatesQueue.value(i.key())); break; - } - } - --_applySkippedLevel; - clearSkippedUpdates(); -} - -void PtsWaiter::clearSkippedUpdates() { - _queue.clear(); - _updateQueue.clear(); - _updatesQueue.clear(); - _applySkippedLevel = 0; -} - -bool PtsWaiter::updated(ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates) { - if (_requesting || _applySkippedLevel) { - return true; - } else if (pts <= _good && count > 0) { - return false; - } else if (check(channel, pts, count)) { - return true; - } - _updatesQueue.insert(ptsKey(SkippedUpdates, pts), updates); - return false; -} - -bool PtsWaiter::updated(ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update) { - if (_requesting || _applySkippedLevel) { - return true; - } else if (pts <= _good && count > 0) { - return false; - } else if (check(channel, pts, count)) { - return true; - } - _updateQueue.insert(ptsKey(SkippedUpdate, pts), update); - return false; -} - -bool PtsWaiter::updated(ChannelData *channel, int32 pts, int32 count) { - if (_requesting || _applySkippedLevel) { - return true; - } else if (pts <= _good && count > 0) { - return false; - } - return check(channel, pts, count); -} - -bool PtsWaiter::updateAndApply(ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates) { - if (!updated(channel, pts, count, updates)) { - return false; - } - if (!_waitingForSkipped || _queue.isEmpty()) { - // Optimization - no need to put in queue and back. - Auth().api().applyUpdatesNoPtsCheck(updates); - } else { - _updatesQueue.insert(ptsKey(SkippedUpdates, pts), updates); - applySkippedUpdates(channel); - } - return true; -} - -bool PtsWaiter::updateAndApply(ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update) { - if (!updated(channel, pts, count, update)) { - return false; - } - if (!_waitingForSkipped || _queue.isEmpty()) { - // Optimization - no need to put in queue and back. - Auth().api().applyUpdateNoPtsCheck(update); - } else { - _updateQueue.insert(ptsKey(SkippedUpdate, pts), update); - applySkippedUpdates(channel); - } - return true; -} - -bool PtsWaiter::updateAndApply(ChannelData *channel, int32 pts, int32 count) { - if (!updated(channel, pts, count)) { - return false; - } - applySkippedUpdates(channel); - return true; -} - -bool PtsWaiter::check(ChannelData *channel, int32 pts, int32 count) { // return false if need to save that update and apply later - if (!inited()) { - init(pts); - return true; - } - - _last = qMax(_last, pts); - _count += count; - if (_last == _count) { - _good = _last; - return true; - } else if (_last < _count) { - setWaitingForSkipped(channel, 1); - } else { - setWaitingForSkipped(channel, WaitForSkippedTimeout); - } - return !count; -} - -PhotoData::PhotoData(const PhotoId &id, const uint64 &access, int32 date, const ImagePtr &thumb, const ImagePtr &medium, const ImagePtr &full) -: id(id) -, access(access) -, date(date) -, thumb(thumb) -, medium(medium) -, full(full) { -} - -void PhotoData::automaticLoad(const HistoryItem *item) { - full->automaticLoad(item); -} - -void PhotoData::automaticLoadSettingsChanged() { - full->automaticLoadSettingsChanged(); -} - -void PhotoData::download() { - full->loadEvenCancelled(); - notifyLayoutChanged(); -} - -bool PhotoData::loaded() const { - bool wasLoading = loading(); - if (full->loaded()) { - if (wasLoading) { - notifyLayoutChanged(); - } - return true; - } - return false; -} - -bool PhotoData::loading() const { - return full->loading(); -} - -bool PhotoData::displayLoading() const { - return full->loading() ? full->displayLoading() : uploading(); -} - -void PhotoData::cancel() { - full->cancel(); - notifyLayoutChanged(); -} - -void PhotoData::notifyLayoutChanged() const { - auto &items = App::photoItems(); - auto i = items.constFind(const_cast(this)); - if (i != items.cend()) { - for_const (auto item, i.value()) { - Notify::historyItemLayoutChanged(item); - } - } -} - -float64 PhotoData::progress() const { - if (uploading()) { - if (uploadingData->size > 0) { - return float64(uploadingData->offset) / uploadingData->size; - } - return 0; - } - return full->progress(); -} - -int32 PhotoData::loadOffset() const { - return full->loadOffset(); -} - -bool PhotoData::uploading() const { - return !!uploadingData; -} - -void PhotoData::forget() { - thumb->forget(); - replyPreview->forget(); - medium->forget(); - full->forget(); -} - -ImagePtr PhotoData::makeReplyPreview() { - if (replyPreview->isNull() && !thumb->isNull()) { - if (thumb->loaded()) { - int w = thumb->width(), h = thumb->height(); - if (w <= 0) w = 1; - if (h <= 0) h = 1; - replyPreview = ImagePtr(w > h ? thumb->pix(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : thumb->pix(st::msgReplyBarSize.height()), "PNG"); - } else { - thumb->load(); - } - } - return replyPreview; -} - -void PhotoOpenClickHandler::onClickImpl() const { - Messenger::Instance().showPhoto(this, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem()); -} - -void PhotoSaveClickHandler::onClickImpl() const { - auto data = photo(); - if (!data->date) return; - - data->download(); -} - -void PhotoCancelClickHandler::onClickImpl() const { - auto data = photo(); - if (!data->date) return; - - if (data->uploading()) { - if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) { - if (auto media = item->getMedia()) { - if (media->type() == MediaTypePhoto && static_cast(media)->photo() == data) { - App::contextItem(item); - App::main()->cancelUploadLayer(); - } - } - } - } else { - data->cancel(); - } -} - -QString joinList(const QStringList &list, const QString &sep) { - QString result; - if (list.isEmpty()) return result; - - int32 l = list.size(), s = sep.size() * (l - 1); - for (int32 i = 0; i < l; ++i) { - s += list.at(i).size(); - } - result.reserve(s); - result.append(list.at(0)); - for (int32 i = 1; i < l; ++i) { - result.append(sep).append(list.at(i)); - } - return result; -} - -QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir) { -#ifdef Q_OS_WIN - name = name.replace(QRegularExpression(qsl("[\\\\\\/\\:\\*\\?\\\"\\<\\>\\|]")), qsl("_")); -#elif defined Q_OS_MAC - name = name.replace(QRegularExpression(qsl("[\\:]")), qsl("_")); -#elif defined Q_OS_LINUX - name = name.replace(QRegularExpression(qsl("[\\/]")), qsl("_")); -#endif - if (Global::AskDownloadPath() || savingAs) { - if (!name.isEmpty() && name.at(0) == QChar::fromLatin1('.')) { - name = filedialogDefaultName(prefix, name); - } else if (dir.path() != qsl(".")) { - QString path = dir.absolutePath(); - if (path != cDialogLastPath()) { - cSetDialogLastPath(path); - Local::writeUserSettings(); - } - } - - // check if extension of filename is present in filter - // it should be in first filter section on the first place - // place it there, if it is not - QString ext = QFileInfo(name).suffix(), fil = filter, sep = qsl(";;"); - if (!ext.isEmpty()) { - if (QRegularExpression(qsl("^[a-zA-Z_0-9]+$")).match(ext).hasMatch()) { - QStringList filters = filter.split(sep); - if (filters.size() > 1) { - QString first = filters.at(0); - int32 start = first.indexOf(qsl("(*.")); - if (start >= 0) { - if (!QRegularExpression(qsl("\\(\\*\\.") + ext + qsl("[\\)\\s]"), QRegularExpression::CaseInsensitiveOption).match(first).hasMatch()) { - QRegularExpressionMatch m = QRegularExpression(qsl(" \\*\\.") + ext + qsl("[\\)\\s]"), QRegularExpression::CaseInsensitiveOption).match(first); - if (m.hasMatch() && m.capturedStart() > start + 3) { - int32 oldpos = m.capturedStart(), oldend = m.capturedEnd(); - fil = first.mid(0, start + 3) + ext + qsl(" *.") + first.mid(start + 3, oldpos - start - 3) + first.mid(oldend - 1) + sep + joinList(filters.mid(1), sep); - } else { - fil = first.mid(0, start + 3) + ext + qsl(" *.") + first.mid(start + 3) + sep + joinList(filters.mid(1), sep); - } - } - } else { - fil = QString(); - } - } else { - fil = QString(); - } - } else { - fil = QString(); - } - } - return filedialogGetSaveFile(name, title, fil, name) ? name : QString(); - } - - QString path; - if (Global::DownloadPath().isEmpty()) { - path = psDownloadPath(); - } else if (Global::DownloadPath() == qsl("tmp")) { - path = cTempDir(); - } else { - path = Global::DownloadPath(); - } - if (name.isEmpty()) name = qsl(".unknown"); - if (name.at(0) == QChar::fromLatin1('.')) { - if (!QDir().exists(path)) QDir().mkpath(path); - return filedialogDefaultName(prefix, name, path); - } - if (dir.path() != qsl(".")) { - path = dir.absolutePath() + '/'; - } - - QString nameStart, extension; - int32 extPos = name.lastIndexOf('.'); - if (extPos >= 0) { - nameStart = name.mid(0, extPos); - extension = name.mid(extPos); - } else { - nameStart = name; - } - QString nameBase = path + nameStart; - name = nameBase + extension; - for (int i = 0; QFileInfo(name).exists(); ++i) { - name = nameBase + QString(" (%1)").arg(i + 2) + extension; - } - - if (!QDir().exists(path)) QDir().mkpath(path); - return name; -} - -bool StickerData::setInstalled() const { - switch (set.type()) { - case mtpc_inputStickerSetID: { - auto it = Global::StickerSets().constFind(set.c_inputStickerSetID().vid.v); - return (it != Global::StickerSets().cend()) && !(it->flags & MTPDstickerSet::Flag::f_archived) && (it->flags & MTPDstickerSet::Flag::f_installed); - } break; - case mtpc_inputStickerSetShortName: { - auto name = qs(set.c_inputStickerSetShortName().vshort_name).toLower(); - for (auto it = Global::StickerSets().cbegin(), e = Global::StickerSets().cend(); it != e; ++it) { - if (it->shortName.toLower() == name) { - return !(it->flags & MTPDstickerSet::Flag::f_archived) && (it->flags & MTPDstickerSet::Flag::f_installed); - } - } - } break; - } - return false; -} - -QString documentSaveFilename(const DocumentData *data, bool forceSavingAs = false, const QString already = QString(), const QDir &dir = QDir()) { - auto alreadySavingFilename = data->loadingFilePath(); - if (!alreadySavingFilename.isEmpty()) { - return alreadySavingFilename; - } - - QString name, filter, caption, prefix; - MimeType mimeType = mimeTypeForName(data->mime); - QStringList p = mimeType.globPatterns(); - QString pattern = p.isEmpty() ? QString() : p.front(); - if (data->voice()) { - bool mp3 = (data->mime == qstr("audio/mp3")); - name = already.isEmpty() ? (mp3 ? qsl(".mp3") : qsl(".ogg")) : already; - filter = mp3 ? qsl("MP3 Audio (*.mp3);;") : qsl("OGG Opus Audio (*.ogg);;"); - filter += FileDialog::AllFilesFilter(); - caption = lang(lng_save_audio); - prefix = qsl("audio"); - } else if (data->isVideo()) { - name = already.isEmpty() ? data->name : already; - if (name.isEmpty()) { - name = pattern.isEmpty() ? qsl(".mov") : pattern.replace('*', QString()); - } - if (pattern.isEmpty()) { - filter = qsl("MOV Video (*.mov);;") + FileDialog::AllFilesFilter(); - } else { - filter = mimeType.filterString() + qsl(";;") + FileDialog::AllFilesFilter(); - } - caption = lang(lng_save_video); - prefix = qsl("video"); - } else { - name = already.isEmpty() ? data->name : already; - if (name.isEmpty()) { - name = pattern.isEmpty() ? qsl(".unknown") : pattern.replace('*', QString()); - } - if (pattern.isEmpty()) { - filter = QString(); - } else { - filter = mimeType.filterString() + qsl(";;") + FileDialog::AllFilesFilter(); - } - caption = lang(data->song() ? lng_save_audio_file : lng_save_file); - prefix = qsl("doc"); - } - - return saveFileName(caption, filter, prefix, name, forceSavingAs, dir); -} - -void DocumentOpenClickHandler::doOpen(DocumentData *data, HistoryItem *context, ActionOnLoad action) { - if (!data->date) return; - - auto msgId = context ? context->fullId() : FullMsgId(); - bool playVoice = data->voice(); - bool playMusic = data->tryPlaySong(); - bool playVideo = data->isVideo(); - bool playAnimation = data->isAnimation(); - auto &location = data->location(true); - if (auto applyTheme = data->isTheme()) { - if (!location.isEmpty() && location.accessEnable()) { - Messenger::Instance().showDocument(data, context); - location.accessDisable(); - return; - } - } - if (!location.isEmpty() || (!data->data().isEmpty() && (playVoice || playMusic || playVideo || playAnimation))) { - using State = Media::Player::State; - if (playVoice) { - auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); - if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) { - if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { - Media::Player::mixer()->resume(state.id); - } else { - Media::Player::mixer()->pause(state.id); - } - } else { - auto audio = AudioMsgId(data, msgId); - Media::Player::mixer()->play(audio); - Media::Player::Updated().notify(audio); - if (App::main()) { - App::main()->mediaMarkRead(data); - } - } - } else if (playMusic) { - auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); - if (state.id == AudioMsgId(data, msgId) && !Media::Player::IsStoppedOrStopping(state.state)) { - if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { - Media::Player::mixer()->resume(state.id); - } else { - Media::Player::mixer()->pause(state.id); - } - } else { - auto song = AudioMsgId(data, msgId); - Media::Player::mixer()->play(song); - Media::Player::Updated().notify(song); - } - } else if (playVideo) { - if (!data->data().isEmpty()) { - Messenger::Instance().showDocument(data, context); - } else if (location.accessEnable()) { - Messenger::Instance().showDocument(data, context); - location.accessDisable(); - } else { - auto filepath = location.name(); - if (documentIsValidMediaFile(filepath)) { - File::Launch(filepath); - } - } - if (App::main()) App::main()->mediaMarkRead(data); - } else if (data->voice() || data->song() || data->isVideo()) { - auto filepath = location.name(); - if (documentIsValidMediaFile(filepath)) { - File::Launch(filepath); - } - if (App::main()) App::main()->mediaMarkRead(data); - } else if (data->size < App::kImageSizeLimit) { - if (!data->data().isEmpty() && playAnimation) { - if (action == ActionOnLoadPlayInline && context && context->getMedia()) { - context->getMedia()->playInline(); - } else { - Messenger::Instance().showDocument(data, context); - } - } else if (location.accessEnable()) { - if (data->isAnimation() || QImageReader(location.name()).canRead()) { - if (action == ActionOnLoadPlayInline && context && context->getMedia()) { - context->getMedia()->playInline(); - } else { - Messenger::Instance().showDocument(data, context); - } - } else { - File::Launch(location.name()); - } - location.accessDisable(); - } else { - File::Launch(location.name()); - } - } else { - File::Launch(location.name()); - } - return; - } - - if (data->status != FileReady) return; - - QString filename; - if (!data->saveToCache()) { - filename = documentSaveFilename(data); - if (filename.isEmpty()) return; - } - - data->save(filename, action, msgId); -} - -void DocumentOpenClickHandler::onClickImpl() const { - auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr); - doOpen(document(), item, document()->voice() ? ActionOnLoadNone : ActionOnLoadOpen); -} - -void GifOpenClickHandler::onClickImpl() const { - auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr); - doOpen(document(), item, ActionOnLoadPlayInline); -} - -void DocumentSaveClickHandler::doSave(DocumentData *data, bool forceSavingAs) { - if (!data->date) return; - - auto filepath = data->filepath(DocumentData::FilePathResolveSaveFromDataSilent, forceSavingAs); - if (!filepath.isEmpty() && !forceSavingAs) { - File::OpenWith(filepath, QCursor::pos()); - } else { - auto fileinfo = QFileInfo(filepath); - auto filedir = filepath.isEmpty() ? QDir() : fileinfo.dir(); - auto filename = filepath.isEmpty() ? QString() : fileinfo.fileName(); - auto newfname = documentSaveFilename(data, forceSavingAs, filename, filedir); - if (!newfname.isEmpty()) { - auto action = (filename.isEmpty() || forceSavingAs) ? ActionOnLoadNone : ActionOnLoadOpenWith; - auto actionMsgId = App::hoveredLinkItem() ? App::hoveredLinkItem()->fullId() : (App::contextItem() ? App::contextItem()->fullId() : FullMsgId()); - data->save(newfname, action, actionMsgId); - } - } -} - -void DocumentSaveClickHandler::onClickImpl() const { - doSave(document()); -} - -void DocumentCancelClickHandler::onClickImpl() const { - auto data = document(); - if (!data->date) return; - - if (data->uploading()) { - if (auto item = App::hoveredLinkItem() ? App::hoveredLinkItem() : (App::contextItem() ? App::contextItem() : nullptr)) { - if (auto media = item->getMedia()) { - if (media->getDocument() == data) { - App::contextItem(item); - App::main()->cancelUploadLayer(); - } - } - } - } else { - data->cancel(); - } -} - -VoiceData::~VoiceData() { - if (!waveform.isEmpty() && waveform.at(0) == -1 && waveform.size() > int32(sizeof(TaskId))) { - TaskId taskId = 0; - memcpy(&taskId, waveform.constData() + 1, sizeof(taskId)); - Local::cancelTask(taskId); - } -} - -DocumentData::DocumentData(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QString &url, const QVector &attributes) -: id(id) -, _dc(dc) -, _access(accessHash) -, _version(version) -, _url(url) { - setattributes(attributes); - if (_dc && _access) { - _location = Local::readFileLocation(mediaKey()); - } -} - -DocumentData *DocumentData::create(DocumentId id) { - return new DocumentData(id, 0, 0, 0, QString(), QVector()); -} - -DocumentData *DocumentData::create(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QVector &attributes) { - return new DocumentData(id, dc, accessHash, version, QString(), attributes); -} - -DocumentData *DocumentData::create(DocumentId id, const QString &url, const QVector &attributes) { - return new DocumentData(id, 0, 0, 0, url, attributes); -} - -void DocumentData::setattributes(const QVector &attributes) { - for (int32 i = 0, l = attributes.size(); i < l; ++i) { - switch (attributes[i].type()) { - case mtpc_documentAttributeImageSize: { - auto &d = attributes[i].c_documentAttributeImageSize(); - dimensions = QSize(d.vw.v, d.vh.v); - } break; - case mtpc_documentAttributeAnimated: if (type == FileDocument || type == StickerDocument || type == VideoDocument) { - type = AnimatedDocument; - _additional = nullptr; - } break; - case mtpc_documentAttributeSticker: { - auto &d = attributes[i].c_documentAttributeSticker(); - if (type == FileDocument) { - type = StickerDocument; - _additional = std::make_unique(); - } - if (sticker()) { - sticker()->alt = qs(d.valt); - if (sticker()->set.type() != mtpc_inputStickerSetID || d.vstickerset.type() == mtpc_inputStickerSetID) { - sticker()->set = d.vstickerset; - } - } - } break; - case mtpc_documentAttributeVideo: { - auto &d = attributes[i].c_documentAttributeVideo(); - if (type == FileDocument) { - type = d.is_round_message() ? RoundVideoDocument : VideoDocument; - } - _duration = d.vduration.v; - dimensions = QSize(d.vw.v, d.vh.v); - } break; - case mtpc_documentAttributeAudio: { - auto &d = attributes[i].c_documentAttributeAudio(); - if (type == FileDocument) { - if (d.is_voice()) { - type = VoiceDocument; - _additional = std::make_unique(); - } else { - type = SongDocument; - _additional = std::make_unique(); - } - } - if (voice()) { - voice()->duration = d.vduration.v; - VoiceWaveform waveform = documentWaveformDecode(qba(d.vwaveform)); - uchar wavemax = 0; - for (int32 i = 0, l = waveform.size(); i < l; ++i) { - uchar waveat = waveform.at(i); - if (wavemax < waveat) wavemax = waveat; - } - voice()->waveform = waveform; - voice()->wavemax = wavemax; - } else if (song()) { - song()->duration = d.vduration.v; - song()->title = qs(d.vtitle); - song()->performer = qs(d.vperformer); - } - } break; - case mtpc_documentAttributeFilename: name = qs(attributes[i].c_documentAttributeFilename().vfile_name); break; - } - } - if (type == StickerDocument) { - if (dimensions.width() <= 0 - || dimensions.height() <= 0 - || dimensions.width() > StickerMaxSize - || dimensions.height() > StickerMaxSize - || !saveToCache()) { - type = FileDocument; - _additional = nullptr; - } - } -} - -bool DocumentData::saveToCache() const { - return (type == StickerDocument && size < Storage::kMaxStickerInMemory) - || (isAnimation() && size < Storage::kMaxAnimationInMemory) - || (voice() && size < Storage::kMaxVoiceInMemory); -} - -void DocumentData::forget() { - thumb->forget(); - if (sticker()) sticker()->img->forget(); - replyPreview->forget(); - _data.clear(); -} - -void DocumentData::automaticLoad(const HistoryItem *item) { - if (loaded() || status != FileReady) return; - - if (saveToCache() && _loader != CancelledMtpFileLoader) { - if (type == StickerDocument) { - save(QString(), _actionOnLoad, _actionOnLoadMsgId); - } else if (isAnimation()) { - bool loadFromCloud = false; - if (item) { - if (item->history()->peer->isUser()) { - loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate); - } else { - loadFromCloud = !(cAutoDownloadGif() & dbiadNoGroups); - } - } else { // if load at least anywhere - loadFromCloud = !(cAutoDownloadGif() & dbiadNoPrivate) || !(cAutoDownloadGif() & dbiadNoGroups); - } - save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); - } else if (voice()) { - if (item) { - bool loadFromCloud = false; - if (item->history()->peer->isUser()) { - loadFromCloud = !(cAutoDownloadAudio() & dbiadNoPrivate); - } else { - loadFromCloud = !(cAutoDownloadAudio() & dbiadNoGroups); - } - save(QString(), _actionOnLoad, _actionOnLoadMsgId, loadFromCloud ? LoadFromCloudOrLocal : LoadFromLocalOnly, true); - } - } - } -} - -void DocumentData::automaticLoadSettingsChanged() { - if (loaded() || status != FileReady || (!isAnimation() && !voice()) || !saveToCache() || _loader != CancelledMtpFileLoader) { - return; - } - _loader = nullptr; -} - -void DocumentData::performActionOnLoad() { - if (_actionOnLoad == ActionOnLoadNone) return; - - auto loc = location(true); - auto already = loc.name(); - auto item = _actionOnLoadMsgId.msg ? App::histItemById(_actionOnLoadMsgId) : nullptr; - auto showImage = !isVideo() && (size < App::kImageSizeLimit); - auto playVoice = voice() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen); - auto playMusic = tryPlaySong() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen); - auto playAnimation = isAnimation() && (_actionOnLoad == ActionOnLoadPlayInline || _actionOnLoad == ActionOnLoadOpen) && showImage && item && item->getMedia(); - if (auto applyTheme = isTheme()) { - if (!loc.isEmpty() && loc.accessEnable()) { - Messenger::Instance().showDocument(this, item); - loc.accessDisable(); - return; - } - } - using State = Media::Player::State; - if (playVoice) { - if (loaded()) { - auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); - if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) { - if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { - Media::Player::mixer()->resume(state.id); - } else { - Media::Player::mixer()->pause(state.id); - } - } else if (Media::Player::IsStopped(state.state)) { - Media::Player::mixer()->play(AudioMsgId(this, _actionOnLoadMsgId)); - if (App::main()) App::main()->mediaMarkRead(this); - } - } - } else if (playMusic) { - if (loaded()) { - auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); - if (state.id == AudioMsgId(this, _actionOnLoadMsgId) && !Media::Player::IsStoppedOrStopping(state.state)) { - if (Media::Player::IsPaused(state.state) || state.state == State::Pausing) { - Media::Player::mixer()->resume(state.id); - } else { - Media::Player::mixer()->pause(state.id); - } - } else if (Media::Player::IsStopped(state.state)) { - auto song = AudioMsgId(this, _actionOnLoadMsgId); - Media::Player::mixer()->play(song); - Media::Player::Updated().notify(song); - } - } - } else if (playAnimation) { - if (loaded()) { - if (_actionOnLoad == ActionOnLoadPlayInline && item->getMedia()) { - item->getMedia()->playInline(); - } else { - Messenger::Instance().showDocument(this, item); - } - } - } else { - if (already.isEmpty()) return; - - if (_actionOnLoad == ActionOnLoadOpenWith) { - File::OpenWith(already, QCursor::pos()); - } else if (_actionOnLoad == ActionOnLoadOpen || _actionOnLoad == ActionOnLoadPlayInline) { - if (voice() || song() || isVideo()) { - if (documentIsValidMediaFile(already)) { - File::Launch(already); - } - if (App::main()) App::main()->mediaMarkRead(this); - } else if (loc.accessEnable()) { - if (showImage && QImageReader(loc.name()).canRead()) { - if (_actionOnLoad == ActionOnLoadPlayInline && item && item->getMedia()) { - item->getMedia()->playInline(); - } else { - Messenger::Instance().showDocument(this, item); - } - } else { - File::Launch(already); - } - loc.accessDisable(); - } else { - File::Launch(already); - } - } - } - _actionOnLoad = ActionOnLoadNone; -} - -bool DocumentData::loaded(FilePathResolveType type) const { - if (loading() && _loader->finished()) { - if (_loader->cancelled()) { - destroyLoaderDelayed(CancelledMtpFileLoader); - } else { - auto that = const_cast(this); - that->_location = FileLocation(_loader->fileName()); - that->_data = _loader->bytes(); - if (that->sticker() && !_loader->imagePixmap().isNull()) { - that->sticker()->img = ImagePtr(_data, _loader->imageFormat(), _loader->imagePixmap()); - } - destroyLoaderDelayed(); - } - notifyLayoutChanged(); - } - return !data().isEmpty() || !filepath(type).isEmpty(); -} - -void DocumentData::destroyLoaderDelayed(mtpFileLoader *newValue) const { - _loader->stop(); - auto loader = std::unique_ptr(std::exchange(_loader, newValue)); - Auth().downloader().delayedDestroyLoader(std::move(loader)); -} - -bool DocumentData::loading() const { - return _loader && _loader != CancelledMtpFileLoader; -} - -QString DocumentData::loadingFilePath() const { - return loading() ? _loader->fileName() : QString(); -} - -bool DocumentData::displayLoading() const { - return loading() ? (!_loader->loadingLocal() || !_loader->autoLoading()) : uploading(); -} - -float64 DocumentData::progress() const { - if (uploading()) { - return snap((size > 0) ? float64(uploadOffset) / size : 0., 0., 1.); - } - return loading() ? _loader->currentProgress() : (loaded() ? 1. : 0.); -} - -int32 DocumentData::loadOffset() const { - return loading() ? _loader->currentOffset() : 0; -} - -bool DocumentData::uploading() const { - return status == FileUploading; -} - -void DocumentData::save(const QString &toFile, ActionOnLoad action, const FullMsgId &actionMsgId, LoadFromCloudSetting fromCloud, bool autoLoading) { - if (loaded(FilePathResolveChecked)) { - auto &l = location(true); - if (!toFile.isEmpty()) { - if (!_data.isEmpty()) { - QFile f(toFile); - f.open(QIODevice::WriteOnly); - f.write(_data); - f.close(); - - setLocation(FileLocation(toFile)); - Local::writeFileLocation(mediaKey(), FileLocation(toFile)); - } else if (l.accessEnable()) { - auto alreadyName = l.name(); - if (alreadyName != toFile) { - QFile(toFile).remove(); - QFile(alreadyName).copy(toFile); - } - l.accessDisable(); - } - } - _actionOnLoad = action; - _actionOnLoadMsgId = actionMsgId; - performActionOnLoad(); - return; - } - - if (_loader == CancelledMtpFileLoader) _loader = nullptr; - if (_loader) { - if (!_loader->setFileName(toFile)) { - cancel(); // changes _actionOnLoad - _loader = nullptr; - } - } - - _actionOnLoad = action; - _actionOnLoadMsgId = actionMsgId; - if (_loader) { - if (fromCloud == LoadFromCloudOrLocal) _loader->permitLoadFromCloud(); - } else { - status = FileReady; - if (!_access && !_url.isEmpty()) { - _loader = new webFileLoader(_url, toFile, fromCloud, autoLoading); - } else { - _loader = new mtpFileLoader(_dc, id, _access, _version, locationType(), toFile, size, (saveToCache() ? LoadToCacheAsWell : LoadToFileOnly), fromCloud, autoLoading); - } - _loader->connect(_loader, SIGNAL(progress(FileLoader*)), App::main(), SLOT(documentLoadProgress(FileLoader*))); - _loader->connect(_loader, SIGNAL(failed(FileLoader*,bool)), App::main(), SLOT(documentLoadFailed(FileLoader*,bool))); - _loader->start(); - } - notifyLayoutChanged(); -} - -void DocumentData::cancel() { - if (!loading()) return; - - auto loader = std::unique_ptr(std::exchange(_loader, CancelledMtpFileLoader)); - loader->cancel(); - loader->stop(); - Auth().downloader().delayedDestroyLoader(std::move(loader)); - - notifyLayoutChanged(); - if (auto main = App::main()) { - main->documentLoadProgress(this); - } - - _actionOnLoad = ActionOnLoadNone; -} - -void DocumentData::notifyLayoutChanged() const { - auto &items = App::documentItems(); - for (auto item : items.value(const_cast(this))) { - Notify::historyItemLayoutChanged(item); - } - - if (auto items = InlineBots::Layout::documentItems()) { - for (auto item : items->value(const_cast(this))) { - item->layoutChanged(); - } - } -} - -VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit) { - auto bitsCount = static_cast(encoded5bit.size() * 8); - auto valuesCount = bitsCount / 5; - if (!valuesCount) { - return VoiceWaveform(); - } - - // Read each 5 bit of encoded5bit as 0-31 unsigned char. - // We count the index of the byte in which the desired 5-bit sequence starts. - // And then we read a uint16 starting from that byte to guarantee to get all of those 5 bits. - // - // BUT! if it is the last byte we have, we're not allowed to read a uint16 starting with it. - // Because it will be an overflow (we'll access one byte after the available memory). - // We see, that only the last 5 bits could start in the last available byte and be problematic. - // So we read in a general way all the entries in a general way except the last one. - auto result = VoiceWaveform(valuesCount, 0); - auto bitsData = encoded5bit.constData(); - for (auto i = 0, l = valuesCount - 1; i != l; ++i) { - auto byteIndex = (i * 5) / 8; - auto bitShift = (i * 5) % 8; - auto value = *reinterpret_cast(bitsData + byteIndex); - result[i] = static_cast((value >> bitShift) & 0x1F); - } - auto lastByteIndex = ((valuesCount - 1) * 5) / 8; - auto lastBitShift = ((valuesCount - 1) * 5) % 8; - auto lastValue = (lastByteIndex == encoded5bit.size() - 1) - ? static_cast(*reinterpret_cast(bitsData + lastByteIndex)) - : *reinterpret_cast(bitsData + lastByteIndex); - result[valuesCount - 1] = static_cast((lastValue >> lastBitShift) & 0x1F); - - return result; -} - -QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform) { - auto bitsCount = waveform.size() * 5; - auto bytesCount = (bitsCount + 7) / 8; - auto result = QByteArray(bytesCount + 1, 0); - auto bitsData = result.data(); - - // Write each 0-31 unsigned char as 5 bit to result. - // We reserve one extra byte to be able to dereference any of required bytes - // as a uint16 without overflowing, even the byte with index "bytesCount - 1". - for (auto i = 0, l = waveform.size(); i < l; ++i) { - auto byteIndex = (i * 5) / 8; - auto bitShift = (i * 5) % 8; - auto value = (static_cast(waveform[i]) & 0x1F) << bitShift; - *reinterpret_cast(bitsData + byteIndex) |= value; - } - result.resize(bytesCount); - return result; -} - -QByteArray DocumentData::data() const { - return _data; -} - -const FileLocation &DocumentData::location(bool check) const { - if (check && !_location.check()) { - const_cast(this)->_location = Local::readFileLocation(mediaKey()); - } - return _location; -} - -void DocumentData::setLocation(const FileLocation &loc) { - if (loc.check()) { - _location = loc; - } -} - -QString DocumentData::filepath(FilePathResolveType type, bool forceSavingAs) const { - bool check = (type != FilePathResolveCached); - QString result = (check && _location.name().isEmpty()) ? QString() : location(check).name(); - bool saveFromData = result.isEmpty() && !data().isEmpty(); - if (saveFromData) { - if (type != FilePathResolveSaveFromData && type != FilePathResolveSaveFromDataSilent) { - saveFromData = false; - } else if (type == FilePathResolveSaveFromDataSilent && (Global::AskDownloadPath() || forceSavingAs)) { - saveFromData = false; - } - } - if (saveFromData) { - QString filename = documentSaveFilename(this, forceSavingAs); - if (!filename.isEmpty()) { - QFile f(filename); - if (f.open(QIODevice::WriteOnly)) { - if (f.write(data()) == data().size()) { - f.close(); - const_cast(this)->_location = FileLocation(filename); - Local::writeFileLocation(mediaKey(), _location); - result = filename; - } - } - } - } - return result; -} - -ImagePtr DocumentData::makeReplyPreview() { - if (replyPreview->isNull() && !thumb->isNull()) { - if (thumb->loaded()) { - int w = thumb->width(), h = thumb->height(); - if (w <= 0) w = 1; - if (h <= 0) h = 1; - auto thumbSize = (w > h) ? QSize(w * st::msgReplyBarSize.height() / h, st::msgReplyBarSize.height()) : QSize(st::msgReplyBarSize.height(), h * st::msgReplyBarSize.height() / w); - thumbSize *= cIntRetinaFactor(); - auto options = Images::Option::Smooth | (isRoundVideo() ? Images::Option::Circled : Images::Option::None) | Images::Option::TransparentBackground; - auto outerSize = st::msgReplyBarSize.height(); - auto image = thumb->pixNoCache(thumbSize.width(), thumbSize.height(), options, outerSize, outerSize); - replyPreview = ImagePtr(image, "PNG"); - } else { - thumb->load(); - } - } - return replyPreview; -} - -bool fileIsImage(const QString &name, const QString &mime) { - QString lowermime = mime.toLower(), namelower = name.toLower(); - if (lowermime.startsWith(qstr("image/"))) { - return true; - } else if (namelower.endsWith(qstr(".bmp")) - || namelower.endsWith(qstr(".jpg")) - || namelower.endsWith(qstr(".jpeg")) - || namelower.endsWith(qstr(".gif")) - || namelower.endsWith(qstr(".webp")) - || namelower.endsWith(qstr(".tga")) - || namelower.endsWith(qstr(".tiff")) - || namelower.endsWith(qstr(".tif")) - || namelower.endsWith(qstr(".psd")) - || namelower.endsWith(qstr(".png"))) { - return true; - } - return false; -} - -void DocumentData::recountIsImage() { - if (isAnimation() || isVideo()) { - return; - } - _duration = fileIsImage(name, mime) ? 1 : -1; // hack -} - -bool DocumentData::setRemoteVersion(int32 version) { - if (_version == version) { - return false; - } - _version = version; - _location = FileLocation(); - _data = QByteArray(); - status = FileReady; - if (loading()) { - destroyLoaderDelayed(); - } - return true; -} - -void DocumentData::setRemoteLocation(int32 dc, uint64 access) { - _dc = dc; - _access = access; - if (isValid()) { - if (_location.check()) { - Local::writeFileLocation(mediaKey(), _location); - } else { - _location = Local::readFileLocation(mediaKey()); - } - } -} - -void DocumentData::setContentUrl(const QString &url) { - _url = url; -} - -void DocumentData::collectLocalData(DocumentData *local) { - if (local == this) return; - - if (!local->_data.isEmpty()) { - _data = local->_data; - if (voice()) { - if (!Local::copyAudio(local->mediaKey(), mediaKey())) { - Local::writeAudio(mediaKey(), _data); - } - } else { - if (!Local::copyStickerImage(local->mediaKey(), mediaKey())) { - Local::writeStickerImage(mediaKey(), _data); - } - } - } - if (!local->_location.isEmpty()) { - _location = local->_location; - Local::writeFileLocation(mediaKey(), _location); - } -} - -DocumentData::~DocumentData() { - if (loading()) { - destroyLoaderDelayed(); - } -} - -QString DocumentData::composeNameString(const QString &filename, const QString &songTitle, const QString &songPerformer) { - if (songTitle.isEmpty() && songPerformer.isEmpty()) { - return filename.isEmpty() ? qsl("Unknown File") : filename; - } - - if (songPerformer.isEmpty()) { - return songTitle; - } - - auto trackTitle = (songTitle.isEmpty() ? qsl("Unknown Track") : songTitle); - return songPerformer + QString::fromUtf8(" \xe2\x80\x93 ") + trackTitle; -} - -WebPageData::WebPageData(const WebPageId &id, WebPageType type, const QString &url, const QString &displayUrl, const QString &siteName, const QString &title, const TextWithEntities &description, DocumentData *document, PhotoData *photo, int32 duration, const QString &author, int32 pendingTill) : id(id) -, type(type) -, url(url) -, displayUrl(displayUrl) -, siteName(siteName) -, title(title) -, description(description) -, duration(duration) -, author(author) -, photo(photo) -, document(document) -, pendingTill(pendingTill) { -} - -GameData::GameData(const GameId &id, const uint64 &accessHash, const QString &shortName, const QString &title, const QString &description, PhotoData *photo, DocumentData *document) : id(id) -, accessHash(accessHash) -, shortName(shortName) -, title(title) -, description(description) -, photo(photo) -, document(document) { -} - -MsgId clientMsgId() { - static MsgId currentClientMsgId = StartClientMsgId; - Assert(currentClientMsgId < EndClientMsgId); - return currentClientMsgId++; -} diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h deleted file mode 100644 index 9f2a0b32c..000000000 --- a/Telegram/SourceFiles/structs.h +++ /dev/null @@ -1,1669 +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 -*/ -#pragma once - -using MediaKey = QPair; - -inline uint64 mediaMix32To64(int32 a, int32 b) { - return (uint64(*reinterpret_cast(&a)) << 32) | uint64(*reinterpret_cast(&b)); -} - -enum LocationType { - UnknownFileLocation = 0, - // 1, 2, etc are used as "version" value in mediaKey() method. - - DocumentFileLocation = 0x4e45abe9, // mtpc_inputDocumentFileLocation - AudioFileLocation = 0x74dc404d, // mtpc_inputAudioFileLocation - VideoFileLocation = 0x3d0364ec, // mtpc_inputVideoFileLocation -}; - -// Old method, should not be used anymore. -//inline MediaKey mediaKey(LocationType type, int32 dc, const uint64 &id) { -// return MediaKey(mediaMix32To64(type, dc), id); -//} -// New method when version was introduced, type is not relevant anymore (all files are Documents). -inline MediaKey mediaKey(LocationType type, int32 dc, const uint64 &id, int32 version) { - return (version > 0) ? MediaKey(mediaMix32To64(version, dc), id) : MediaKey(mediaMix32To64(type, dc), id); -} - -inline StorageKey mediaKey(const MTPDfileLocation &location) { - return storageKey(location.vdc_id.v, location.vvolume_id.v, location.vlocal_id.v); -} - -using UserId = int32; -using ChatId = int32; -using ChannelId = int32; -constexpr auto NoChannel = ChannelId(0); - -using MsgId = int32; -constexpr auto StartClientMsgId = MsgId(-0x7FFFFFFF); -constexpr auto EndClientMsgId = MsgId(-0x40000000); -constexpr auto ShowAtTheEndMsgId = MsgId(-0x40000000); -constexpr auto SwitchAtTopMsgId = MsgId(-0x3FFFFFFF); -constexpr auto ShowAtProfileMsgId = MsgId(-0x3FFFFFFE); -constexpr auto ShowAndStartBotMsgId = MsgId(-0x3FFFFFD); -constexpr auto ShowAtGameShareMsgId = MsgId(-0x3FFFFFC); -constexpr auto ServerMaxMsgId = MsgId(0x3FFFFFFF); -constexpr auto ShowAtUnreadMsgId = MsgId(0); -constexpr inline bool IsClientMsgId(MsgId id) { - return (id >= StartClientMsgId && id < EndClientMsgId); -} -constexpr inline bool IsServerMsgId(MsgId id) { - return (id > 0 && id < ServerMaxMsgId); -} - -struct MsgRange { - MsgRange() = default; - MsgRange(MsgId from, MsgId till) : from(from), till(till) { - } - - MsgId from = 0; - MsgId till = 0; -}; -inline bool operator==(const MsgRange &a, const MsgRange &b) { - return (a.from == b.from) && (a.till == b.till); -} -inline bool operator!=(const MsgRange &a, const MsgRange &b) { - return !(a == b); -} - -struct FullMsgId { - FullMsgId() = default; - FullMsgId(ChannelId channel, MsgId msg) : channel(channel), msg(msg) { - } - explicit operator bool() const { - return msg != 0; - } - ChannelId channel = NoChannel; - MsgId msg = 0; -}; -inline bool operator==(const FullMsgId &a, const FullMsgId &b) { - return (a.channel == b.channel) && (a.msg == b.msg); -} -inline bool operator!=(const FullMsgId &a, const FullMsgId &b) { - return !(a == b); -} -inline bool operator<(const FullMsgId &a, const FullMsgId &b) { - if (a.msg < b.msg) return true; - if (a.msg > b.msg) return false; - return a.channel < b.channel; -} - -using PeerId = uint64; -constexpr auto PeerIdMask = PeerId(0xFFFFFFFFULL); -constexpr auto PeerIdTypeMask = PeerId(0x300000000ULL); -constexpr auto PeerIdUserShift = PeerId(0x000000000ULL); -constexpr auto PeerIdChatShift = PeerId(0x100000000ULL); -constexpr auto PeerIdChannelShift = PeerId(0x200000000ULL); -inline bool peerIsUser(const PeerId &id) { - return (id & PeerIdTypeMask) == PeerIdUserShift; -} -inline bool peerIsChat(const PeerId &id) { - return (id & PeerIdTypeMask) == PeerIdChatShift; -} -inline bool peerIsChannel(const PeerId &id) { - return (id & PeerIdTypeMask) == PeerIdChannelShift; -} -inline PeerId peerFromUser(UserId user_id) { - return PeerIdUserShift | uint64(uint32(user_id)); -} -inline PeerId peerFromChat(ChatId chat_id) { - return PeerIdChatShift | uint64(uint32(chat_id)); -} -inline PeerId peerFromChannel(ChannelId channel_id) { - return PeerIdChannelShift | uint64(uint32(channel_id)); -} -inline PeerId peerFromUser(const MTPint &user_id) { - return peerFromUser(user_id.v); -} -inline PeerId peerFromChat(const MTPint &chat_id) { - return peerFromChat(chat_id.v); -} -inline PeerId peerFromChannel(const MTPint &channel_id) { - return peerFromChannel(channel_id.v); -} -inline int32 peerToBareInt(const PeerId &id) { - return int32(uint32(id & PeerIdMask)); -} -inline UserId peerToUser(const PeerId &id) { - return peerIsUser(id) ? peerToBareInt(id) : 0; -} -inline ChatId peerToChat(const PeerId &id) { - return peerIsChat(id) ? peerToBareInt(id) : 0; -} -inline ChannelId peerToChannel(const PeerId &id) { - return peerIsChannel(id) ? peerToBareInt(id) : NoChannel; -} -inline MTPint peerToBareMTPInt(const PeerId &id) { - return MTP_int(peerToBareInt(id)); -} -inline PeerId peerFromMTP(const MTPPeer &peer) { - switch (peer.type()) { - case mtpc_peerUser: return peerFromUser(peer.c_peerUser().vuser_id); - case mtpc_peerChat: return peerFromChat(peer.c_peerChat().vchat_id); - case mtpc_peerChannel: return peerFromChannel(peer.c_peerChannel().vchannel_id); - } - return 0; -} -inline MTPpeer peerToMTP(const PeerId &id) { - if (peerIsUser(id)) { - return MTP_peerUser(peerToBareMTPInt(id)); - } else if (peerIsChat(id)) { - return MTP_peerChat(peerToBareMTPInt(id)); - } else if (peerIsChannel(id)) { - return MTP_peerChannel(peerToBareMTPInt(id)); - } - return MTP_peerUser(MTP_int(0)); -} -inline PeerId peerFromMessage(const MTPmessage &msg) { - auto compute = [](auto &message) { - auto from_id = message.has_from_id() ? peerFromUser(message.vfrom_id) : 0; - auto to_id = peerFromMTP(message.vto_id); - auto out = message.is_out(); - return (out || !peerIsUser(to_id)) ? to_id : from_id; - }; - switch (msg.type()) { - case mtpc_message: return compute(msg.c_message()); - case mtpc_messageService: return compute(msg.c_messageService()); - } - return 0; -} -inline MTPDmessage::Flags flagsFromMessage(const MTPmessage &msg) { - switch (msg.type()) { - case mtpc_message: return msg.c_message().vflags.v; - case mtpc_messageService: return mtpCastFlags(msg.c_messageService().vflags.v); - } - return 0; -} -inline MsgId idFromMessage(const MTPmessage &msg) { - switch (msg.type()) { - case mtpc_messageEmpty: return msg.c_messageEmpty().vid.v; - case mtpc_message: return msg.c_message().vid.v; - case mtpc_messageService: return msg.c_messageService().vid.v; - } - Unexpected("Type in idFromMessage()"); -} -inline TimeId dateFromMessage(const MTPmessage &msg) { - switch (msg.type()) { - case mtpc_message: return msg.c_message().vdate.v; - case mtpc_messageService: return msg.c_messageService().vdate.v; - } - return 0; -} - -using PhotoId = uint64; -using VideoId = uint64; -using AudioId = uint64; -using DocumentId = uint64; -using WebPageId = uint64; -using GameId = uint64; -constexpr auto CancelledWebPageId = WebPageId(0xFFFFFFFFFFFFFFFFULL); - -struct NotifySettings { - NotifySettings() : flags(MTPDpeerNotifySettings::Flag::f_show_previews), sound(qsl("default")) { - } - MTPDpeerNotifySettings::Flags flags; - TimeId mute = 0; - QString sound; - bool previews() const { - return flags & MTPDpeerNotifySettings::Flag::f_show_previews; - } - bool silent() const { - return flags & MTPDpeerNotifySettings::Flag::f_silent; - } -}; -typedef NotifySettings *NotifySettingsPtr; - -static const NotifySettingsPtr UnknownNotifySettings = NotifySettingsPtr(0); -static const NotifySettingsPtr EmptyNotifySettings = NotifySettingsPtr(1); -extern NotifySettings globalNotifyAll, globalNotifyUsers, globalNotifyChats; -extern NotifySettingsPtr globalNotifyAllPtr, globalNotifyUsersPtr, globalNotifyChatsPtr; - -inline bool isNotifyMuted(NotifySettingsPtr settings, TimeId *changeIn = nullptr) { - if (settings != UnknownNotifySettings && settings != EmptyNotifySettings) { - auto t = unixtime(); - if (settings->mute > t) { - if (changeIn) *changeIn = settings->mute - t + 1; - return true; - } - } - if (changeIn) *changeIn = 0; - return false; -} - -static constexpr int kUserColorsCount = 8; -static constexpr int kChatColorsCount = 4; -static constexpr int kChannelColorsCount = 4; - -class EmptyUserpic { -public: - EmptyUserpic(); - EmptyUserpic(int index, const QString &name); - - void set(int index, const QString &name); - void clear(); - - explicit operator bool() const { - return (_impl != nullptr); - } - - void paint(Painter &p, int x, int y, int outerWidth, int size) const; - void paintRounded(Painter &p, int x, int y, int outerWidth, int size) const; - void paintSquare(Painter &p, int x, int y, int outerWidth, int size) const; - QPixmap generate(int size); - StorageKey uniqueKey() const; - - ~EmptyUserpic(); - -private: - class Impl; - std::unique_ptr _impl; - friend class Impl; - -}; - -static const PhotoId UnknownPeerPhotoId = 0xFFFFFFFFFFFFFFFFULL; - -inline const QString &emptyUsername() { - static QString empty; - return empty; -} - -class PeerData; - -class PeerClickHandler : public ClickHandler { -public: - PeerClickHandler(not_null peer); - void onClick(Qt::MouseButton button) const override; - - not_null peer() const { - return _peer; - } - -private: - not_null _peer; - -}; - -class UserData; -class ChatData; -class ChannelData; - -class PeerData { -protected: - PeerData(const PeerId &id); - PeerData(const PeerData &other) = delete; - PeerData &operator=(const PeerData &other) = delete; - -public: - virtual ~PeerData() { - if (notify != UnknownNotifySettings && notify != EmptyNotifySettings) { - delete base::take(notify); - } - } - - bool isUser() const { - return peerIsUser(id); - } - bool isChat() const { - return peerIsChat(id); - } - bool isChannel() const { - return peerIsChannel(id); - } - bool isSelf() const { - return (input.type() == mtpc_inputPeerSelf); - } - bool isVerified() const; - bool isMegagroup() const; - bool isMuted() const { - return (notify != EmptyNotifySettings) && (notify != UnknownNotifySettings) && (notify->mute >= unixtime()); - } - bool canWrite() const; - UserData *asUser(); - const UserData *asUser() const; - ChatData *asChat(); - const ChatData *asChat() const; - ChannelData *asChannel(); - const ChannelData *asChannel() const; - ChannelData *asMegagroup(); - const ChannelData *asMegagroup() const; - - ChatData *migrateFrom() const; - ChannelData *migrateTo() const; - - void updateFull(); - void updateFullForced(); - void fullUpdated(); - bool wasFullUpdated() const { - return (_lastFullUpdate != 0); - } - - const Text &dialogName() const; - const QString &shortName() const; - const QString &userName() const; - - const PeerId id; - int32 bareId() const { - return int32(uint32(id & 0xFFFFFFFFULL)); - } - - QString name; - Text nameText; - using Names = OrderedSet; - Names names; // for filtering - using NameFirstChars = OrderedSet; - NameFirstChars chars; - - enum LoadedStatus { - NotLoaded = 0x00, - MinimalLoaded = 0x01, - FullLoaded = 0x02, - }; - LoadedStatus loadedStatus = NotLoaded; - MTPinputPeer input; - - int colorIndex() const { - return _colorIndex; - } - void setUserpic(ImagePtr userpic); - void paintUserpic(Painter &p, int x, int y, int size) const; - void paintUserpicLeft(Painter &p, int x, int y, int w, int size) const { - paintUserpic(p, rtl() ? (w - x - size) : x, y, size); - } - void paintUserpicRounded(Painter &p, int x, int y, int size) const; - void paintUserpicSquare(Painter &p, int x, int y, int size) const; - void loadUserpic(bool loadFirst = false, bool prior = true) { - _userpic->load(loadFirst, prior); - } - bool userpicLoaded() const { - return _userpic->loaded(); - } - StorageKey userpicUniqueKey() const; - void saveUserpic(const QString &path, int size) const; - void saveUserpicRounded(const QString &path, int size) const; - QPixmap genUserpic(int size) const; - QPixmap genUserpicRounded(int size) const; - - PhotoId photoId = UnknownPeerPhotoId; - StorageImageLocation photoLoc; - - int nameVersion = 1; - - NotifySettingsPtr notify = UnknownNotifySettings; - - // if this string is not empty we must not allow to open the - // conversation and we must show this string instead - virtual QString restrictionReason() const { - return QString(); - } - - ClickHandlerPtr createOpenLink(); - const ClickHandlerPtr &openLink() { - if (!_openLink) { - _openLink = createOpenLink(); - } - return _openLink; - } - - ImagePtr currentUserpic() const; - -protected: - void updateNameDelayed(const QString &newName, const QString &newNameOrPhone, const QString &newUsername); - - ImagePtr _userpic; - mutable EmptyUserpic _userpicEmpty; - -private: - void fillNames(); - - ClickHandlerPtr _openLink; - - int _colorIndex = 0; - TimeMs _lastFullUpdate = 0; - -}; - -class BotCommand { -public: - BotCommand(const QString &command, const QString &description) : command(command), _description(description) { - } - QString command; - - bool setDescription(const QString &description) { - if (_description != description) { - _description = description; - _descriptionText = Text(); - return true; - } - return false; - } - - const Text &descriptionText() const; - -private: - QString _description; - mutable Text _descriptionText; - -}; - -struct BotInfo { - bool inited = false; - bool readsAllHistory = false; - bool cantJoinGroups = false; - int version = 0; - QString description, inlinePlaceholder; - QList commands; - Text text = Text{ int(st::msgMinWidth) }; // description - - QString startToken, startGroupToken, shareGameShortName; - PeerId inlineReturnPeerId = 0; -}; - -class PhotoData; -class UserData : public PeerData { -public: - UserData(const PeerId &id) : PeerData(id) { - } - void setPhoto(const MTPUserProfilePhoto &photo); - - void setName(const QString &newFirstName, const QString &newLastName - , const QString &newPhoneName, const QString &newUsername); - - void setPhone(const QString &newPhone); - void setBotInfoVersion(int version); - void setBotInfo(const MTPBotInfo &info); - - void setNameOrPhone(const QString &newNameOrPhone); - - void madeAction(TimeId when); // pseudo-online - - uint64 access = 0; - - MTPDuser::Flags flags = 0; - bool isVerified() const { - return flags & MTPDuser::Flag::f_verified; - } - bool isBotInlineGeo() const { - return flags & MTPDuser::Flag::f_bot_inline_geo; - } - bool isInaccessible() const { - return (access == NoAccess); - } - void setIsInaccessible() { - access = NoAccess; - } - bool canWrite() const { - return !isInaccessible(); - } - bool isContact() const { - return (contact > 0); - } - - bool canShareThisContact() const; - bool canAddContact() const { - return canShareThisContact() && !isContact(); - } - - // In feedUsers() we check only that. - // When actually trying to share contact we perform - // a full check by canShareThisContact() call. - bool canShareThisContactFast() const { - return !_phone.isEmpty(); - } - - MTPInputUser inputUser; - - QString firstName; - QString lastName; - QString username; - const QString &phone() const { - return _phone; - } - QString nameOrPhone; - Text phoneText; - TimeId onlineTill = 0; - int32 contact = -1; // -1 - not contact, cant add (self, empty, deleted, foreign), 0 - not contact, can add (request), 1 - contact - - enum class BlockStatus { - Unknown, - Blocked, - NotBlocked, - }; - BlockStatus blockStatus() const { - return _blockStatus; - } - bool isBlocked() const { - return (blockStatus() == BlockStatus::Blocked); - } - void setBlockStatus(BlockStatus blockStatus); - - enum class CallsStatus { - Unknown, - Enabled, - Disabled, - Private, - }; - CallsStatus callsStatus() const { - return _callsStatus; - } - bool hasCalls() const; - void setCallsStatus(CallsStatus callsStatus); - - bool setAbout(const QString &newAbout); - const QString &about() const { - return _about; - } - - std::unique_ptr botInfo; - - QString restrictionReason() const override { - return _restrictionReason; - } - void setRestrictionReason(const QString &reason); - - int commonChatsCount() const { - return _commonChatsCount; - } - void setCommonChatsCount(int count); - -private: - QString _restrictionReason; - QString _about; - QString _phone; - BlockStatus _blockStatus = BlockStatus::Unknown; - CallsStatus _callsStatus = CallsStatus::Unknown; - int _commonChatsCount = 0; - - static constexpr const uint64 NoAccess = 0xFFFFFFFFFFFFFFFFULL; - -}; - -class ChatData : public PeerData { -public: - ChatData(const PeerId &id) : PeerData(id), inputChat(MTP_int(bareId())) { - } - void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = UnknownPeerPhotoId); - - void setName(const QString &newName); - - void invalidateParticipants(); - bool noParticipantInfo() const { - return (count > 0 || amIn()) && participants.isEmpty(); - } - - MTPint inputChat; - - ChannelData *migrateToPtr = nullptr; - - int count = 0; - TimeId date = 0; - int version = 0; - UserId creator = 0; - - MTPDchat::Flags flags = 0; - bool isForbidden() const { - return _isForbidden; - } - void setIsForbidden(bool forbidden) { - _isForbidden = forbidden; - } - bool amIn() const { - return !isForbidden() && !haveLeft() && !wasKicked(); - } - bool canEdit() const { - return !isDeactivated() && (amCreator() || (adminsEnabled() ? amAdmin() : amIn())); - } - bool canWrite() const { - return !isDeactivated() && amIn(); - } - bool haveLeft() const { - return flags & MTPDchat::Flag::f_left; - } - bool wasKicked() const { - return flags & MTPDchat::Flag::f_kicked; - } - bool adminsEnabled() const { - return flags & MTPDchat::Flag::f_admins_enabled; - } - bool amCreator() const { - return flags & MTPDchat::Flag::f_creator; - } - bool amAdmin() const { - return (flags & MTPDchat::Flag::f_admin) && adminsEnabled(); - } - bool isDeactivated() const { - return flags & MTPDchat::Flag::f_deactivated; - } - bool isMigrated() const { - return flags & MTPDchat::Flag::f_migrated_to; - } - QMap, int> participants; - OrderedSet> invitedByMe; - OrderedSet> admins; - QList> lastAuthors; - OrderedSet> markupSenders; - int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other -// ImagePtr photoFull; - - void setInviteLink(const QString &newInviteLink); - QString inviteLink() const { - return _inviteLink; - } - -private: - bool _isForbidden = false; - QString _inviteLink; - -}; - -enum PtsSkippedQueue { - SkippedUpdate, - SkippedUpdates, -}; -class PtsWaiter { -public: - - PtsWaiter() - : _good(0) - , _last(0) - , _count(0) - , _applySkippedLevel(0) - , _requesting(false) - , _waitingForSkipped(false) - , _waitingForShortPoll(false) { - } - void init(int32 pts) { - _good = _last = _count = pts; - clearSkippedUpdates(); - } - bool inited() const { - return _good > 0; - } - void setRequesting(bool isRequesting) { - _requesting = isRequesting; - if (_requesting) { - clearSkippedUpdates(); - } - } - bool requesting() const { - return _requesting; - } - bool waitingForSkipped() const { - return _waitingForSkipped; - } - bool waitingForShortPoll() const { - return _waitingForShortPoll; - } - void setWaitingForSkipped(ChannelData *channel, int32 ms); // < 0 - not waiting - void setWaitingForShortPoll(ChannelData *channel, int32 ms); // < 0 - not waiting - int32 current() const{ - return _good; - } - bool updated(ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates); - bool updated(ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update); - bool updated(ChannelData *channel, int32 pts, int32 count); - bool updateAndApply(ChannelData *channel, int32 pts, int32 count, const MTPUpdates &updates); - bool updateAndApply(ChannelData *channel, int32 pts, int32 count, const MTPUpdate &update); - bool updateAndApply(ChannelData *channel, int32 pts, int32 count); - void applySkippedUpdates(ChannelData *channel); - void clearSkippedUpdates(); - -private: - bool check(ChannelData *channel, int32 pts, int32 count); // return false if need to save that update and apply later - uint64 ptsKey(PtsSkippedQueue queue, int32 pts); - void checkForWaiting(ChannelData *channel); - QMap _queue; - QMap _updateQueue; - QMap _updatesQueue; - int32 _good, _last, _count; - int32 _applySkippedLevel; - bool _requesting, _waitingForSkipped, _waitingForShortPoll; - uint32 _skippedKey = 0; -}; - -struct MegagroupInfo { - struct Admin { - explicit Admin(MTPChannelAdminRights rights) : rights(rights) { - } - Admin(MTPChannelAdminRights rights, bool canEdit) : rights(rights), canEdit(canEdit) { - } - MTPChannelAdminRights rights; - bool canEdit = false; - }; - struct Restricted { - explicit Restricted(MTPChannelBannedRights rights) : rights(rights) { - } - MTPChannelBannedRights rights; - }; - QList> lastParticipants; - QMap, Admin> lastAdmins; - QMap, Restricted> lastRestricted; - OrderedSet> markupSenders; - OrderedSet> bots; - - UserData *creator = nullptr; // nullptr means unknown - int botStatus = 0; // -1 - no bots, 0 - unknown, 1 - one bot, that sees all history, 2 - other - MsgId pinnedMsgId = 0; - bool joinedMessageFound = false; - MTPInputStickerSet stickerSet = MTP_inputStickerSetEmpty(); - - enum LastParticipantsStatus { - LastParticipantsUpToDate = 0x00, - LastParticipantsAdminsOutdated = 0x01, - LastParticipantsCountOutdated = 0x02, - }; - mutable int lastParticipantsStatus = LastParticipantsUpToDate; - int lastParticipantsCount = 0; - - ChatData *migrateFromPtr = nullptr; - -}; - -class ChannelData : public PeerData { -public: - ChannelData(const PeerId &id) : PeerData(id), inputChannel(MTP_inputChannel(MTP_int(bareId()), MTP_long(0))) { - } - void setPhoto(const MTPChatPhoto &photo, const PhotoId &phId = UnknownPeerPhotoId); - - void setName(const QString &name, const QString &username); - - uint64 access = 0; - - MTPinputChannel inputChannel; - - QString username; - - // Returns true if about text was changed. - bool setAbout(const QString &newAbout); - const QString &about() const { - return _about; - } - - int membersCount() const { - return _membersCount; - } - void setMembersCount(int newMembersCount); - - int adminsCount() const { - return _adminsCount; - } - void setAdminsCount(int newAdminsCount); - - int restrictedCount() const { - return _restrictedCount; - } - void setRestrictedCount(int newRestrictedCount); - - int kickedCount() const { - return _kickedCount; - } - void setKickedCount(int newKickedCount); - - bool haveLeft() const { - return flags & MTPDchannel::Flag::f_left; - } - bool amIn() const { - return !isForbidden() && !haveLeft(); - } - bool addsSignature() const { - return flags & MTPDchannel::Flag::f_signatures; - } - bool isForbidden() const { - return _isForbidden; - } - void setIsForbidden(bool forbidden) { - _isForbidden = forbidden; - } - bool isVerified() const { - return flags & MTPDchannel::Flag::f_verified; - } - - static MTPChannelBannedRights KickedRestrictedRights(); - static constexpr auto kRestrictUntilForever = TimeId(INT_MAX); - static bool IsRestrictedForever(TimeId until) { - return !until || (until == kRestrictUntilForever); - } - void applyEditAdmin(not_null user, const MTPChannelAdminRights &oldRights, const MTPChannelAdminRights &newRights); - void applyEditBanned(not_null user, const MTPChannelBannedRights &oldRights, const MTPChannelBannedRights &newRights); - - int32 date = 0; - int version = 0; - MTPDchannel::Flags flags = 0; - MTPDchannelFull::Flags flagsFull = 0; - std::unique_ptr mgInfo; - bool lastParticipantsCountOutdated() const { - if (!mgInfo || !(mgInfo->lastParticipantsStatus & MegagroupInfo::LastParticipantsCountOutdated)) { - return false; - } - if (mgInfo->lastParticipantsCount == membersCount()) { - mgInfo->lastParticipantsStatus &= ~MegagroupInfo::LastParticipantsCountOutdated; - return false; - } - return true; - } - void flagsUpdated(); - bool isMegagroup() const { - return flags & MTPDchannel::Flag::f_megagroup; - } - bool isBroadcast() const { - return flags & MTPDchannel::Flag::f_broadcast; - } - bool isPublic() const { - return flags & MTPDchannel::Flag::f_username; - } - bool amCreator() const { - return flags & MTPDchannel::Flag::f_creator; - } - const MTPChannelAdminRights &adminRightsBoxed() const { - return _adminRights; - } - const MTPDchannelAdminRights &adminRights() const { - return _adminRights.c_channelAdminRights(); - } - void setAdminRights(const MTPChannelAdminRights &rights); - bool hasAdminRights() const { - return (adminRights().vflags.v != 0); - } - const MTPChannelBannedRights &restrictedRightsBoxed() const { - return _restrictedRights; - } - const MTPDchannelBannedRights &restrictedRights() const { - return _restrictedRights.c_channelBannedRights(); - } - void setRestrictedRights(const MTPChannelBannedRights &rights); - bool hasRestrictedRights() const { - return (restrictedRights().vflags.v != 0); - } - bool hasRestrictedRights(int32 now) const { - return hasRestrictedRights() && (restrictedRights().vuntil_date.v > now); - } - bool canBanMembers() const { - return adminRights().is_ban_users() || amCreator(); - } - bool canEditMessages() const { - return adminRights().is_edit_messages() || amCreator(); - } - bool canDeleteMessages() const { - return adminRights().is_delete_messages() || amCreator(); - } - bool anyoneCanAddMembers() const { - return (flags & MTPDchannel::Flag::f_democracy); - } - bool canAddMembers() const { - return adminRights().is_invite_users() || amCreator() || (anyoneCanAddMembers() && amIn() && !hasRestrictedRights()); - } - bool canAddAdmins() const { - return adminRights().is_add_admins() || amCreator(); - } - bool canPinMessages() const { - return adminRights().is_pin_messages() || amCreator(); - } - bool canPublish() const { - return adminRights().is_post_messages() || amCreator(); - } - bool canWrite() const { - return amIn() && (canPublish() || (!isBroadcast() && !restrictedRights().is_send_messages())); - } - bool canViewMembers() const { - return flagsFull & MTPDchannelFull::Flag::f_can_view_participants; - } - bool canViewAdmins() const { - return (isMegagroup() || hasAdminRights() || amCreator()); - } - bool canViewBanned() const { - return (hasAdminRights() || amCreator()); - } - bool canEditInformation() const { - return adminRights().is_change_info() || amCreator(); - } - bool canEditUsername() const { - return amCreator() && (flagsFull & MTPDchannelFull::Flag::f_can_set_username); - } - bool canEditStickers() const { - return (flagsFull & MTPDchannelFull::Flag::f_can_set_stickers); - } - bool canDelete() const { - constexpr auto kDeleteChannelMembersLimit = 1000; - return amCreator() && (membersCount() <= kDeleteChannelMembersLimit); - } - bool canEditAdmin(not_null user) const; - bool canRestrictUser(not_null user) const; - - void setInviteLink(const QString &newInviteLink); - QString inviteLink() const { - return _inviteLink; - } - bool canHaveInviteLink() const { - return adminRights().is_invite_link() || amCreator(); - } - - int32 inviter = 0; // > 0 - user who invited me to channel, < 0 - not in channel - QDateTime inviteDate; - - void ptsInit(int32 pts) { - _ptsWaiter.init(pts); - } - void ptsReceived(int32 pts) { - _ptsWaiter.updateAndApply(this, pts, 0); - } - bool ptsUpdateAndApply(int32 pts, int32 count) { - return _ptsWaiter.updateAndApply(this, pts, count); - } - bool ptsUpdateAndApply(int32 pts, int32 count, const MTPUpdate &update) { - return _ptsWaiter.updateAndApply(this, pts, count, update); - } - bool ptsUpdateAndApply(int32 pts, int32 count, const MTPUpdates &updates) { - return _ptsWaiter.updateAndApply(this, pts, count, updates); - } - int32 pts() const { - return _ptsWaiter.current(); - } - bool ptsInited() const { - return _ptsWaiter.inited(); - } - bool ptsRequesting() const { - return _ptsWaiter.requesting(); - } - void ptsSetRequesting(bool isRequesting) { - return _ptsWaiter.setRequesting(isRequesting); - } - void ptsWaitingForShortPoll(int32 ms) { // < 0 - not waiting - return _ptsWaiter.setWaitingForShortPoll(this, ms); - } - bool ptsWaitingForSkipped() const { - return _ptsWaiter.waitingForSkipped(); - } - bool ptsWaitingForShortPoll() const { - return _ptsWaiter.waitingForShortPoll(); - } - - QString restrictionReason() const override { - return _restrictionReason; - } - void setRestrictionReason(const QString &reason); - -private: - bool canNotEditLastAdmin(not_null user) const; - - PtsWaiter _ptsWaiter; - - bool _isForbidden = true; - int _membersCount = 1; - int _adminsCount = 1; - int _restrictedCount = 0; - int _kickedCount = 0; - - MTPChannelAdminRights _adminRights = MTP_channelAdminRights(MTP_flags(0)); - MTPChannelBannedRights _restrictedRights = MTP_channelBannedRights(MTP_flags(0), MTP_int(0)); - - QString _restrictionReason; - QString _about; - - QString _inviteLink; - -}; - -inline bool isUser(const PeerData *peer) { - return peer ? peer->isUser() : false; -} -inline UserData *PeerData::asUser() { - return isUser() ? static_cast(this) : nullptr; -} -inline UserData *asUser(PeerData *peer) { - return peer ? peer->asUser() : nullptr; -} -inline const UserData *PeerData::asUser() const { - return isUser() ? static_cast(this) : nullptr; -} -inline const UserData *asUser(const PeerData *peer) { - return peer ? peer->asUser() : nullptr; -} -inline bool isChat(const PeerData *peer) { - return peer ? peer->isChat() : false; -} -inline ChatData *PeerData::asChat() { - return isChat() ? static_cast(this) : nullptr; -} -inline ChatData *asChat(PeerData *peer) { - return peer ? peer->asChat() : nullptr; -} -inline const ChatData *PeerData::asChat() const { - return isChat() ? static_cast(this) : nullptr; -} -inline const ChatData *asChat(const PeerData *peer) { - return peer ? peer->asChat() : nullptr; -} -inline bool isChannel(const PeerData *peer) { - return peer ? peer->isChannel() : false; -} -inline ChannelData *PeerData::asChannel() { - return isChannel() ? static_cast(this) : nullptr; -} -inline ChannelData *asChannel(PeerData *peer) { - return peer ? peer->asChannel() : nullptr; -} -inline const ChannelData *PeerData::asChannel() const { - return isChannel() ? static_cast(this) : nullptr; -} -inline const ChannelData *asChannel(const PeerData *peer) { - return peer ? peer->asChannel() : nullptr; -} -inline ChannelData *PeerData::asMegagroup() { - return isMegagroup() ? static_cast(this) : nullptr; -} -inline ChannelData *asMegagroup(PeerData *peer) { - return peer ? peer->asMegagroup() : nullptr; -} -inline const ChannelData *PeerData::asMegagroup() const { - return isMegagroup() ? static_cast(this) : nullptr; -} -inline const ChannelData *asMegagroup(const PeerData *peer) { - return peer ? peer->asMegagroup() : nullptr; -} -inline bool isMegagroup(const PeerData *peer) { - return peer ? peer->isMegagroup() : false; -} -inline ChatData *PeerData::migrateFrom() const { - return (isMegagroup() && asChannel()->amIn()) ? asChannel()->mgInfo->migrateFromPtr : nullptr; -} -inline ChannelData *PeerData::migrateTo() const { - return (isChat() && asChat()->migrateToPtr && asChat()->migrateToPtr->amIn()) ? asChat()->migrateToPtr : nullptr; -} -inline const Text &PeerData::dialogName() const { - return migrateTo() ? migrateTo()->dialogName() : ((isUser() && !asUser()->phoneText.isEmpty()) ? asUser()->phoneText : nameText); -} -inline const QString &PeerData::shortName() const { - return isUser() ? asUser()->firstName : name; -} -inline const QString &PeerData::userName() const { - return isUser() ? asUser()->username : (isChannel() ? asChannel()->username : emptyUsername()); -} -inline bool PeerData::isVerified() const { - return isUser() ? asUser()->isVerified() : (isChannel() ? asChannel()->isVerified() : false); -} -inline bool PeerData::isMegagroup() const { - return isChannel() ? asChannel()->isMegagroup() : false; -} -inline bool PeerData::canWrite() const { - return isChannel() ? asChannel()->canWrite() : (isChat() ? asChat()->canWrite() : (isUser() ? asUser()->canWrite() : false)); -} - -enum ActionOnLoad { - ActionOnLoadNone, - ActionOnLoadOpen, - ActionOnLoadOpenWith, - ActionOnLoadPlayInline -}; - -typedef QMap PreparedPhotoThumbs; -class PhotoData { -public: - PhotoData(const PhotoId &id, const uint64 &access = 0, int32 date = 0, const ImagePtr &thumb = ImagePtr(), const ImagePtr &medium = ImagePtr(), const ImagePtr &full = ImagePtr()); - - void automaticLoad(const HistoryItem *item); - void automaticLoadSettingsChanged(); - - void download(); - bool loaded() const; - bool loading() const; - bool displayLoading() const; - void cancel(); - float64 progress() const; - int32 loadOffset() const; - bool uploading() const; - - void forget(); - ImagePtr makeReplyPreview(); - - PhotoId id; - uint64 access; - int32 date; - ImagePtr thumb, replyPreview; - ImagePtr medium; - ImagePtr full; - - PeerData *peer = nullptr; // for chat and channel photos connection - // geo, caption - - struct UploadingData { - UploadingData(int size) : size(size) { - } - int offset = 0; - int size = 0; - }; - std::unique_ptr uploadingData; - -private: - void notifyLayoutChanged() const; - -}; - -class PhotoClickHandler : public LeftButtonClickHandler { -public: - PhotoClickHandler(not_null photo, PeerData *peer = nullptr) : _photo(photo), _peer(peer) { - } - not_null photo() const { - return _photo; - } - PeerData *peer() const { - return _peer; - } - -private: - not_null _photo; - PeerData *_peer; - -}; - -class PhotoOpenClickHandler : public PhotoClickHandler { -public: - using PhotoClickHandler::PhotoClickHandler; -protected: - void onClickImpl() const override; -}; - -class PhotoSaveClickHandler : public PhotoClickHandler { -public: - using PhotoClickHandler::PhotoClickHandler; -protected: - void onClickImpl() const override; -}; - -class PhotoCancelClickHandler : public PhotoClickHandler { -public: - using PhotoClickHandler::PhotoClickHandler; -protected: - void onClickImpl() const override; -}; - -enum FileStatus { - FileDownloadFailed = -2, - FileUploadFailed = -1, - FileUploading = 0, - FileReady = 1, -}; - -// Don't change the values. This type is used for serialization. -enum DocumentType { - FileDocument = 0, - VideoDocument = 1, - SongDocument = 2, - StickerDocument = 3, - AnimatedDocument = 4, - VoiceDocument = 5, - RoundVideoDocument = 6, -}; - -struct DocumentAdditionalData { - virtual ~DocumentAdditionalData() = default; - -}; - -struct StickerData : public DocumentAdditionalData { - ImagePtr img; - QString alt; - - MTPInputStickerSet set = MTP_inputStickerSetEmpty(); - bool setInstalled() const; - - StorageImageLocation loc; // doc thumb location - -}; - -struct SongData : public DocumentAdditionalData { - int32 duration = 0; - QString title, performer; - -}; - -typedef QVector VoiceWaveform; // [0] == -1 -- counting, [0] == -2 -- could not count -struct VoiceData : public DocumentAdditionalData { - ~VoiceData(); - - int duration = 0; - VoiceWaveform waveform; - char wavemax = 0; -}; - -bool fileIsImage(const QString &name, const QString &mime); - -namespace Serialize { -class Document; -} // namespace Serialize; - -class DocumentData { -public: - static DocumentData *create(DocumentId id); - static DocumentData *create(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QVector &attributes); - static DocumentData *create(DocumentId id, const QString &url, const QVector &attributes); - - void setattributes(const QVector &attributes); - - void automaticLoad(const HistoryItem *item); // auto load sticker or video - void automaticLoadSettingsChanged(); - - enum FilePathResolveType { - FilePathResolveCached, - FilePathResolveChecked, - FilePathResolveSaveFromData, - FilePathResolveSaveFromDataSilent, - }; - bool loaded(FilePathResolveType type = FilePathResolveCached) const; - bool loading() const; - QString loadingFilePath() const; - bool displayLoading() const; - void save(const QString &toFile, ActionOnLoad action = ActionOnLoadNone, const FullMsgId &actionMsgId = FullMsgId(), LoadFromCloudSetting fromCloud = LoadFromCloudOrLocal, bool autoLoading = false); - void cancel(); - float64 progress() const; - int32 loadOffset() const; - bool uploading() const; - - QByteArray data() const; - const FileLocation &location(bool check = false) const; - void setLocation(const FileLocation &loc); - - QString filepath(FilePathResolveType type = FilePathResolveCached, bool forceSavingAs = false) const; - - bool saveToCache() const; - - void performActionOnLoad(); - - void forget(); - ImagePtr makeReplyPreview(); - - StickerData *sticker() { - return (type == StickerDocument) ? static_cast(_additional.get()) : nullptr; - } - void checkSticker() { - StickerData *s = sticker(); - if (!s) return; - - automaticLoad(nullptr); - if (s->img->isNull() && loaded()) { - if (_data.isEmpty()) { - const FileLocation &loc(location(true)); - if (loc.accessEnable()) { - s->img = ImagePtr(loc.name()); - loc.accessDisable(); - } - } else { - s->img = ImagePtr(_data); - } - } - } - SongData *song() { - return (type == SongDocument) ? static_cast(_additional.get()) : nullptr; - } - const SongData *song() const { - return const_cast(this)->song(); - } - VoiceData *voice() { - return (type == VoiceDocument) ? static_cast(_additional.get()) : nullptr; - } - const VoiceData *voice() const { - return const_cast(this)->voice(); - } - bool isRoundVideo() const { - return (type == RoundVideoDocument); - } - bool isAnimation() const { - return (type == AnimatedDocument) || isRoundVideo() || !mime.compare(qstr("image/gif"), Qt::CaseInsensitive); - } - bool isGifv() const { - return (type == AnimatedDocument) && !mime.compare(qstr("video/mp4"), Qt::CaseInsensitive); - } - bool isTheme() const { - return name.endsWith(qstr(".tdesktop-theme"), Qt::CaseInsensitive) || name.endsWith(qstr(".tdesktop-palette"), Qt::CaseInsensitive); - } - bool tryPlaySong() const { - return (song() != nullptr) || mime.startsWith(qstr("audio/"), Qt::CaseInsensitive); - } - bool isMusic() const { - if (auto s = song()) { - return (s->duration > 0); - } - return false; - } - bool isVideo() const { - return (type == VideoDocument); - } - int32 duration() const { - return (isAnimation() || isVideo()) ? _duration : -1; - } - bool isImage() const { - return !isAnimation() && !isVideo() && (_duration > 0); - } - void recountIsImage(); - void setData(const QByteArray &data) { - _data = data; - } - - bool setRemoteVersion(int32 version); // Returns true if version has changed. - void setRemoteLocation(int32 dc, uint64 access); - void setContentUrl(const QString &url); - bool hasRemoteLocation() const { - return (_dc != 0 && _access != 0); - } - bool isValid() const { - return hasRemoteLocation() || !_url.isEmpty(); - } - MTPInputDocument mtpInput() const { - if (_access) { - return MTP_inputDocument(MTP_long(id), MTP_long(_access)); - } - return MTP_inputDocumentEmpty(); - } - - // When we have some client-side generated document - // (for example for displaying an external inline bot result) - // and it has downloaded data, we can collect that data from it - // to (this) received from the server "same" document. - void collectLocalData(DocumentData *local); - - ~DocumentData(); - - DocumentId id = 0; - DocumentType type = FileDocument; - QSize dimensions; - int32 date = 0; - QString name; - QString mime; - ImagePtr thumb, replyPreview; - int32 size = 0; - - FileStatus status = FileReady; - int32 uploadOffset = 0; - - int32 md5[8]; - - MediaKey mediaKey() const { - return ::mediaKey(locationType(), _dc, id, _version); - } - - static QString composeNameString(const QString &filename, const QString &songTitle, const QString &songPerformer); - QString composeNameString() const { - if (auto songData = song()) { - return composeNameString(name, songData->title, songData->performer); - } - return composeNameString(name, QString(), QString()); - } - -private: - DocumentData(DocumentId id, int32 dc, uint64 accessHash, int32 version, const QString &url, const QVector &attributes); - - friend class Serialize::Document; - - LocationType locationType() const { - return voice() ? AudioFileLocation : (isVideo() ? VideoFileLocation : DocumentFileLocation); - } - - // Two types of location: from MTProto by dc+access+version or from web by url - int32 _dc = 0; - uint64 _access = 0; - int32 _version = 0; - QString _url; - - FileLocation _location; - QByteArray _data; - std::unique_ptr _additional; - int32 _duration = -1; - - ActionOnLoad _actionOnLoad = ActionOnLoadNone; - FullMsgId _actionOnLoadMsgId; - mutable FileLoader *_loader = nullptr; - - void notifyLayoutChanged() const; - - void destroyLoaderDelayed(mtpFileLoader *newValue = nullptr) const; - -}; - -VoiceWaveform documentWaveformDecode(const QByteArray &encoded5bit); -QByteArray documentWaveformEncode5bit(const VoiceWaveform &waveform); - -class AudioMsgId { -public: - enum class Type { - Unknown, - Voice, - Song, - Video, - }; - - AudioMsgId() = default; - AudioMsgId(DocumentData *audio, const FullMsgId &msgId, uint32 playId = 0) : _audio(audio), _contextId(msgId), _playId(playId) { - setTypeFromAudio(); - } - - Type type() const { - return _type; - } - DocumentData *audio() const { - return _audio; - } - FullMsgId contextId() const { - return _contextId; - } - uint32 playId() const { - return _playId; - } - - explicit operator bool() const { - return _audio != nullptr; - } - -private: - void setTypeFromAudio() { - if (_audio->voice() || _audio->isRoundVideo()) { - _type = Type::Voice; - } else if (_audio->isVideo()) { - _type = Type::Video; - } else if (_audio->tryPlaySong()) { - _type = Type::Song; - } else { - _type = Type::Unknown; - } - } - - DocumentData *_audio = nullptr; - Type _type = Type::Unknown; - FullMsgId _contextId; - uint32 _playId = 0; - -}; - -inline bool operator<(const AudioMsgId &a, const AudioMsgId &b) { - if (quintptr(a.audio()) < quintptr(b.audio())) { - return true; - } else if (quintptr(b.audio()) < quintptr(a.audio())) { - return false; - } else if (a.contextId() < b.contextId()) { - return true; - } else if (b.contextId() < a.contextId()) { - return false; - } - return (a.playId() < b.playId()); -} -inline bool operator==(const AudioMsgId &a, const AudioMsgId &b) { - return a.audio() == b.audio() && a.contextId() == b.contextId() && a.playId() == b.playId(); -} -inline bool operator!=(const AudioMsgId &a, const AudioMsgId &b) { - return !(a == b); -} - -class DocumentClickHandler : public LeftButtonClickHandler { -public: - DocumentClickHandler(DocumentData *document) : _document(document) { - } - DocumentData *document() const { - return _document; - } - -private: - DocumentData *_document; - -}; - -class DocumentSaveClickHandler : public DocumentClickHandler { -public: - using DocumentClickHandler::DocumentClickHandler; - static void doSave(DocumentData *document, bool forceSavingAs = false); -protected: - void onClickImpl() const override; -}; - -class DocumentOpenClickHandler : public DocumentClickHandler { -public: - using DocumentClickHandler::DocumentClickHandler; - static void doOpen(DocumentData *document, HistoryItem *context, ActionOnLoad action = ActionOnLoadOpen); -protected: - void onClickImpl() const override; -}; - -class GifOpenClickHandler : public DocumentOpenClickHandler { -public: - using DocumentOpenClickHandler::DocumentOpenClickHandler; -protected: - void onClickImpl() const override; -}; - -class VoiceSeekClickHandler : public DocumentOpenClickHandler { -public: - using DocumentOpenClickHandler::DocumentOpenClickHandler; -protected: - void onClickImpl() const override { - } -}; - -class DocumentCancelClickHandler : public DocumentClickHandler { -public: - using DocumentClickHandler::DocumentClickHandler; -protected: - void onClickImpl() const override; -}; - -enum WebPageType { - WebPagePhoto, - WebPageVideo, - WebPageProfile, - WebPageArticle -}; -inline WebPageType toWebPageType(const QString &type) { - if (type == qstr("photo")) return WebPagePhoto; - if (type == qstr("video")) return WebPageVideo; - if (type == qstr("profile")) return WebPageProfile; - return WebPageArticle; -} - -struct WebPageData { - WebPageData(const WebPageId &id, WebPageType type = WebPageArticle, const QString &url = QString(), const QString &displayUrl = QString(), const QString &siteName = QString(), const QString &title = QString(), const TextWithEntities &description = TextWithEntities(), DocumentData *doc = nullptr, PhotoData *photo = nullptr, int32 duration = 0, const QString &author = QString(), int32 pendingTill = -1); - - void forget() { - if (document) document->forget(); - if (photo) photo->forget(); - } - - WebPageId id; - WebPageType type; - QString url, displayUrl, siteName, title; - TextWithEntities description; - int32 duration; - QString author; - PhotoData *photo; - DocumentData *document; - int32 pendingTill; - -}; - -struct GameData { - GameData(const GameId &id, const uint64 &accessHash = 0, const QString &shortName = QString(), const QString &title = QString(), const QString &description = QString(), PhotoData *photo = nullptr, DocumentData *doc = nullptr); - - void forget() { - if (document) document->forget(); - if (photo) photo->forget(); - } - - GameId id; - uint64 accessHash; - QString shortName, title, description; - PhotoData *photo; - DocumentData *document; - -}; - -QString saveFileName(const QString &title, const QString &filter, const QString &prefix, QString name, bool savingAs, const QDir &dir = QDir()); -MsgId clientMsgId(); - -struct MessageCursor { - MessageCursor() = default; - MessageCursor(int position, int anchor, int scroll) : position(position), anchor(anchor), scroll(scroll) { - } - MessageCursor(const QTextEdit *edit) { - fillFrom(edit); - } - void fillFrom(const QTextEdit *edit) { - QTextCursor c = edit->textCursor(); - position = c.position(); - anchor = c.anchor(); - QScrollBar *s = edit->verticalScrollBar(); - scroll = (s && (s->value() != s->maximum())) ? s->value() : QFIXED_MAX; - } - void applyTo(QTextEdit *edit) { - auto cursor = edit->textCursor(); - cursor.setPosition(anchor, QTextCursor::MoveAnchor); - cursor.setPosition(position, QTextCursor::KeepAnchor); - edit->setTextCursor(cursor); - if (auto scrollbar = edit->verticalScrollBar()) { - scrollbar->setValue(scroll); - } - } - int position = 0; - int anchor = 0; - int scroll = QFIXED_MAX; - -}; - -inline bool operator==(const MessageCursor &a, const MessageCursor &b) { - return (a.position == b.position) && (a.anchor == b.anchor) && (a.scroll == b.scroll); -} - -struct SendAction { - enum class Type { - Typing, - RecordVideo, - UploadVideo, - RecordVoice, - UploadVoice, - RecordRound, - UploadRound, - UploadPhoto, - UploadFile, - ChooseLocation, - ChooseContact, - PlayGame, - }; - SendAction(Type type, TimeMs until, int progress = 0) : type(type), until(until), progress(progress) { - } - Type type; - TimeMs until; - int progress; -}; diff --git a/Telegram/SourceFiles/ui/text/text_entity.cpp b/Telegram/SourceFiles/ui/text/text_entity.cpp index cdb8c81b6..0ccdc34bc 100644 --- a/Telegram/SourceFiles/ui/text/text_entity.cpp +++ b/Telegram/SourceFiles/ui/text/text_entity.cpp @@ -1465,7 +1465,9 @@ EntitiesInText EntitiesFromMTP(const QVector &entities) { auto &d = entity.c_messageEntityMentionName(); auto data = [&d] { if (auto user = App::userLoaded(peerFromUser(d.vuser_id))) { - return MentionNameDataFromFields({ d.vuser_id.v, user->access }); + return MentionNameDataFromFields({ + d.vuser_id.v, + user->accessHash() }); } return MentionNameDataFromFields(d.vuser_id.v); }; diff --git a/Telegram/SourceFiles/window/window_controller.cpp b/Telegram/SourceFiles/window/window_controller.cpp index a57318fc0..e16025d44 100644 --- a/Telegram/SourceFiles/window/window_controller.cpp +++ b/Telegram/SourceFiles/window/window_controller.cpp @@ -300,7 +300,8 @@ void Controller::showPeerInfo( PeerId peerId, anim::type animated, anim::activation activation) { - if (Adaptive::ThreeColumn()) { + if (Adaptive::ThreeColumn() + && !Auth().data().thirdSectionInfoEnabled()) { Auth().data().setThirdSectionInfoEnabled(true); Auth().saveDataDelayed(kThirdSectionInfoTimeoutMs); } diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 8ab0dea8a..e140d4bbe 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -141,8 +141,19 @@ <(src_loc)/core/version.h <(src_loc)/data/data_abstract_structure.cpp <(src_loc)/data/data_abstract_structure.h +<(src_loc)/data/data_document.cpp +<(src_loc)/data/data_document.h <(src_loc)/data/data_drafts.cpp <(src_loc)/data/data_drafts.h +<(src_loc)/data/data_flags.h +<(src_loc)/data/data_game.h +<(src_loc)/data/data_peer.cpp +<(src_loc)/data/data_peer.h +<(src_loc)/data/data_photo.cpp +<(src_loc)/data/data_photo.h +<(src_loc)/data/data_types.cpp +<(src_loc)/data/data_types.h +<(src_loc)/data/data_web_page.h <(src_loc)/dialogs/dialogs_common.h <(src_loc)/dialogs/dialogs_indexed_list.cpp <(src_loc)/dialogs/dialogs_indexed_list.h @@ -646,8 +657,6 @@ <(src_loc)/shortcuts.h <(src_loc)/stdafx.cpp <(src_loc)/stdafx.h -<(src_loc)/structs.cpp -<(src_loc)/structs.h <(emoji_suggestions_loc)/emoji_suggestions.cpp <(emoji_suggestions_loc)/emoji_suggestions.h