diff --git a/Telegram/SourceFiles/base/match_method.h b/Telegram/SourceFiles/base/match_method.h new file mode 100644 index 00000000000000..9553ed116a3d1c --- /dev/null +++ b/Telegram/SourceFiles/base/match_method.h @@ -0,0 +1,28 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include + +namespace base { + +template +inline decltype(auto) match_method( + Data &&data, + Method &&method, + Methods &&...methods) { + if constexpr (rpl::details::is_callable_plain_v) { + return std::forward(method)(std::forward(data)); + } else { + return match_method( + std::forward(data), + std::forward(methods)...); + } +} + +} // namespace base diff --git a/Telegram/SourceFiles/base/optional.h b/Telegram/SourceFiles/base/optional.h index bc25131ee7e4e3..8fb16e9032f8cf 100644 --- a/Telegram/SourceFiles/base/optional.h +++ b/Telegram/SourceFiles/base/optional.h @@ -109,6 +109,15 @@ class optional_variant { return _impl.template get_unchecked(); } + template + decltype(auto) match(Methods &&...methods) { + return base::match(_impl, std::forward(methods)...); + } + template + decltype(auto) match(Methods &&...methods) const { + return base::match(_impl, std::forward(methods)...); + } + private: variant _impl; @@ -124,6 +133,20 @@ inline const T *get_if(const optional_variant *v) { return (v && v->template is()) ? &v->template get_unchecked() : nullptr; } +template +inline decltype(auto) match( + optional_variant &value, + Methods &&...methods) { + return value.match(std::forward(methods)...); +} + +template +inline decltype(auto) match( + const optional_variant &value, + Methods &&...methods) { + return value.match(std::forward(methods)...); +} + template class optional; diff --git a/Telegram/SourceFiles/base/variant.h b/Telegram/SourceFiles/base/variant.h index f9e6b669e1ced0..058724b6c4fbe2 100644 --- a/Telegram/SourceFiles/base/variant.h +++ b/Telegram/SourceFiles/base/variant.h @@ -8,6 +8,9 @@ For license and copyright information please follow this link: #pragma once #include +#include +#include "base/match_method.h" +#include "base/assertion.h" // We use base::variant<> alias and base::get_if() helper while we don't have std::variant<>. namespace base { @@ -25,20 +28,79 @@ inline const T *get_if(const variant *v) { return (v && v->template is()) ? &v->template get_unchecked() : nullptr; } -// Simplified visit -template -inline auto visit(Method &&method, const variant &value) { - return value.match(std::forward(method)); -} +namespace type_list = rpl::details::type_list; + +template +struct normalized_variant { + using list = type_list::list; + using distinct = type_list::distinct_t; + using type = std::conditional_t< + type_list::size_v == 1, + type_list::get_t<0, distinct>, + type_list::extract_to_t>; +}; + +template +using normalized_variant_t + = typename normalized_variant::type; + +template +struct match_helper; + +template < + typename Type, + typename ...Types, + typename Variant, + typename ...Methods> +struct match_helper, Variant, Methods...> { + static decltype(auto) call(Variant &value, Methods &&...methods) { + if (const auto v = get_if(&value)) { + return match_method( + *v, + std::forward(methods)...); + } + return match_helper< + type_list::list, + Variant, + Methods...>::call( + value, + std::forward(methods)...); + } +}; + +template < + typename Type, + typename Variant, + typename ...Methods> +struct match_helper, Variant, Methods...> { + static decltype(auto) call(Variant &value, Methods &&...methods) { + if (const auto v = get_if(&value)) { + return match_method( + *v, + std::forward(methods)...); + } + Unexpected("Valueless variant in base::match()."); + } +}; -template -inline auto visit(Method &&method, variant &value) { - return value.match(std::forward(method)); +template +inline decltype(auto) match( + variant &value, + Methods &&...methods) { + return match_helper< + type_list::list, + variant, + Methods...>::call(value, std::forward(methods)...); } -template -inline auto visit(Method &&method, variant &&value) { - return value.match(std::forward(method)); +template +inline decltype(auto) match( + const variant &value, + Methods &&...methods) { + return match_helper< + type_list::list, + const variant, + Methods...>::call(value, std::forward(methods)...); } } // namespace base diff --git a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py index 7bce245d9f273d..a6a9b1afdedcc3 100644 --- a/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py +++ b/Telegram/SourceFiles/codegen/scheme/codegen_scheme.py @@ -635,7 +635,7 @@ def addTextSerializeInit(lst, dct): switchLines += '\tcase mtpc_' + name + ': '; # for by-type-id type constructor getters += '\tconst MTPD' + name + ' &c_' + name + '() const;\n'; # const getter - visitor += '\tcase mtpc_' + name + ': return VisitData(c_' + name + '(), std::forward(callback), std::forward(callbacks)...);\n'; + visitor += '\tcase mtpc_' + name + ': return base::match_method(c_' + name + '(), std::forward(method), std::forward(methods)...);\n'; forwards += 'class MTPD' + name + ';\n'; # data class forward declaration if (len(prms) > len(trivialConditions)): @@ -771,14 +771,14 @@ def addTextSerializeInit(lst, dct): typesText += getters; if (withType): typesText += '\n'; - typesText += '\ttemplate \n'; - typesText += '\tdecltype(auto) visit(Callback &&callback, Callbacks &&...callbacks) const;\n'; - visitorMethods += 'template \n'; - visitorMethods += 'decltype(auto) MTP' + restype + '::visit(Callback &&callback, Callbacks &&...callbacks) const {\n'; + typesText += '\ttemplate \n'; + typesText += '\tdecltype(auto) match(Method &&method, Methods &&...methods) const;\n'; + visitorMethods += 'template \n'; + visitorMethods += 'decltype(auto) MTP' + restype + '::match(Method &&method, Methods &&...methods) const {\n'; visitorMethods += '\tswitch (_type) {\n'; visitorMethods += visitor; visitorMethods += '\t}\n'; - visitorMethods += '\tUnexpected("Type in MTP' + restype + '::visit.");\n'; + visitorMethods += '\tUnexpected("Type in MTP' + restype + '::match.");\n'; visitorMethods += '}\n\n'; typesText += '\n\tuint32 innerLength() const;\n'; # size method diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 91e4e606bec0de..90fda141c3dc08 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -7,7 +7,10 @@ For license and copyright information please follow this link: */ #include "export/data/export_data_types.h" +#include "core/mime_type.h" + #include +#include namespace App { // Hackish.. QString formatPhone(QString phone); @@ -22,6 +25,12 @@ constexpr auto kChatPeerIdShift = (2ULL << 32); } // namespace +QString PreparePhotoFileName(TimeId date) { + return "Photo_" + + QString::fromUtf8(FormatDateTime(date, '_', '_', '_')) + + ".jpg"; +} + PeerId UserPeerId(int32 userId) { return kUserPeerIdShift | uint32(userId); } @@ -35,7 +44,7 @@ int32 BarePeerId(PeerId peerId) { } PeerId ParsePeerId(const MTPPeer &data) { - return data.visit([](const MTPDpeerUser &data) { + return data.match([](const MTPDpeerUser &data) { return UserPeerId(data.vuser_id.v); }, [](const MTPDpeerChat &data) { return ChatPeerId(data.vchat_id.v); @@ -62,7 +71,7 @@ Utf8String FillLeft(const Utf8String &data, int length, char filler) { } FileLocation ParseLocation(const MTPFileLocation &data) { - return data.visit([](const MTPDfileLocation &data) { + return data.match([](const MTPDfileLocation &data) { return FileLocation{ data.vdc_id.v, MTP_inputFileLocation( @@ -81,38 +90,38 @@ FileLocation ParseLocation(const MTPFileLocation &data) { }); } -File ParseMaxImage( +Image ParseMaxImage( const MTPVector &data, const QString &suggestedPath) { - auto result = File(); - result.suggestedPath = suggestedPath; + auto result = Image(); + result.file.suggestedPath = suggestedPath; auto maxArea = int64(0); for (const auto &size : data.v) { - size.visit([&](const MTPDphotoSize &data) { - const auto area = data.vw.v * int64(data.vh.v); - if (area > maxArea) { - result.location = ParseLocation(data.vlocation); - result.size = data.vsize.v; - result.content = QByteArray(); - maxArea = area; - } - }, [&](const MTPDphotoCachedSize &data) { + size.match([](const MTPDphotoSizeEmpty &) { + }, [&](const auto &data) { const auto area = data.vw.v * int64(data.vh.v); if (area > maxArea) { - result.location = ParseLocation(data.vlocation); - result.size = data.vbytes.v.size(); - result.content = data.vbytes.v; + result.width = data.vw.v; + result.height = data.vh.v; + result.file.location = ParseLocation(data.vlocation); + if constexpr (MTPDphotoCachedSize::Is()) { + result.file.content = data.vbytes.v; + result.file.size = result.file.content.size(); + } else { + result.file.content = QByteArray(); + result.file.size = data.vsize.v; + } maxArea = area; } - }, [](const MTPDphotoSizeEmpty &) {}); + }); } return result; } Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) { auto result = Photo(); - data.visit([&](const MTPDphoto &data) { + data.match([&](const MTPDphoto &data) { result.id = data.vid.v; result.date = data.vdate.v; result.image = ParseMaxImage(data.vsizes, suggestedPath); @@ -122,6 +131,147 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) { return result; } +void ParseAttributes( + Document &result, + const MTPVector &attributes) { + for (const auto &value : attributes.v) { + value.match([&](const MTPDdocumentAttributeImageSize &data) { + result.width = data.vw.v; + result.height = data.vh.v; + }, [&](const MTPDdocumentAttributeAnimated &data) { + result.isAnimated = true; + }, [&](const MTPDdocumentAttributeSticker &data) { + result.stickerEmoji = ParseString(data.valt); + }, [&](const MTPDdocumentAttributeVideo &data) { + if (data.is_round_message()) { + result.isVideoMessage = true; + } else { + result.isVideoFile = true; + } + result.width = data.vw.v; + result.height = data.vh.v; + result.duration = data.vduration.v; + }, [&](const MTPDdocumentAttributeAudio &data) { + if (data.is_voice()) { + result.isVoiceMessage = true; + } else { + result.isAudioFile = true; + } + result.songPerformer = ParseString(data.vperformer); + result.songTitle = ParseString(data.vtitle); + result.duration = data.vduration.v; + }, [&](const MTPDdocumentAttributeFilename &data) { + result.name = ParseString(data.vfile_name); + }, [&](const MTPDdocumentAttributeHasStickers &data) { + }); + } +} + +QString ComputeDocumentName(const Document &data, TimeId date) { + if (!data.name.isEmpty()) { + return QString::fromUtf8(data.name); + } + const auto mimeString = QString::fromUtf8(data.mime); + const auto mimeType = Core::MimeTypeForName(mimeString); + const auto hasMimeType = [&](QLatin1String mime) { + return !mimeString.compare(mime, Qt::CaseInsensitive); + }; + const auto patterns = mimeType.globPatterns(); + auto pattern = patterns.isEmpty() ? QString() : patterns.front(); + auto extension = QString(); + auto prefix = QString(); + if (data.isVoiceMessage) { + const auto isMP3 = hasMimeType(qstr("audio/mp3")); + extension = isMP3 ? qsl(".mp3") : qsl(".ogg"); + prefix = qsl("Audio_"); + } else if (data.isVideoFile) { + extension = pattern.isEmpty() + ? qsl(".mov") + : QString(pattern).replace('*', QString()); + prefix = qsl("Video_"); + } else { + extension = pattern.isEmpty() + ? qsl(".unknown") + : pattern.replace('*', QString()); + prefix = qsl("File_"); + } + return prefix + + QString::fromUtf8(FormatDateTime(date, '_', '_', '_')) + + extension; +} + +QString CleanDocumentName(QString name) { + // We don't want LTR/RTL mark/embedding/override/isolate chars + // in filenames, because they introduce a security issue, when + // an executable "Fil[x]gepj.exe" may look like "Filexe.jpeg". + QChar controls[] = { + 0x200E, // LTR Mark + 0x200F, // RTL Mark + 0x202A, // LTR Embedding + 0x202B, // RTL Embedding + 0x202D, // LTR Override + 0x202E, // RTL Override + 0x2066, // LTR Isolate + 0x2067, // RTL Isolate +#ifdef Q_OS_WIN + '\\', + '/', + ':', + '*', + '?', + '"', + '<', + '>', + '|', +#elif defined Q_OS_MAC // Q_OS_WIN + ':', +#elif defined Q_OS_LINUX // Q_OS_WIN || Q_OS_MAC + '/', +#endif // Q_OS_WIN || Q_OS_MAC || Q_OS_LINUX + }; + for (const auto ch : controls) { + name = std::move(name).replace(ch, '_'); + } + +#ifdef Q_OS_WIN + const auto lower = name.trimmed().toLower(); + const auto kBadExtensions = { qstr(".lnk"), qstr(".scf") }; + const auto kMaskExtension = qsl(".download"); + for (const auto extension : kBadExtensions) { + if (lower.endsWith(extension)) { + return name + kMaskExtension; + } + } +#endif // Q_OS_WIN + + return name; +} + +Document ParseDocument( + const MTPDocument &data, + const QString &suggestedFolder, + TimeId date) { + auto result = Document(); + data.match([&](const MTPDdocument &data) { + result.id = data.vid.v; + result.date = data.vdate.v; + result.file.size = data.vsize.v; + result.file.location.dcId = data.vdc_id.v; + result.file.location.data = MTP_inputDocumentFileLocation( + data.vid, + data.vaccess_hash, + data.vversion); + result.mime = ParseString(data.vmime_type); + ParseAttributes(result, data.vattributes); + result.file.suggestedPath = suggestedFolder + + CleanDocumentName( + ComputeDocumentName(result, date ? date : result.date)); + }, [&](const MTPDdocumentEmpty &data) { + result.id = data.vid.v; + }); + return result; +} + Utf8String FormatDateTime( TimeId date, QChar dateSeparator, @@ -144,12 +294,10 @@ UserpicsSlice ParseUserpicsSlice(const MTPVector &data) { auto result = UserpicsSlice(); result.list.reserve(list.size()); for (const auto &photo : list) { - const auto suggestedPath = "PersonalPhotos/Photo_" + const auto suggestedPath = "PersonalPhotos/" + (photo.type() == mtpc_photo - ? QString::fromUtf8( - FormatDateTime(photo.c_photo().vdate.v, '_', '_', '_')) - : "Empty") - + ".jpg"; + ? PreparePhotoFileName(photo.c_photo().vdate.v) + : "Photo_Empty.jpg"); result.list.push_back(ParsePhoto(photo, suggestedPath)); } return result; @@ -157,7 +305,7 @@ UserpicsSlice ParseUserpicsSlice(const MTPVector &data) { User ParseUser(const MTPUser &data) { auto result = User(); - data.visit([&](const MTPDuser &data) { + data.match([&](const MTPDuser &data) { result.id = data.vid.v; if (data.has_first_name()) { result.firstName = ParseString(data.vfirst_name); @@ -193,7 +341,7 @@ std::map ParseUsersList(const MTPVector &data) { Chat ParseChat(const MTPChat &data) { auto result = Chat(); - data.visit([&](const MTPDchat &data) { + data.match([&](const MTPDchat &data) { result.id = data.vid.v; result.title = ParseString(data.vtitle); result.input = MTP_inputPeerChat(MTP_int(result.id)); @@ -293,15 +441,125 @@ std::map ParsePeersLists( return result; } -Message ParseMessage(const MTPMessage &data) { +File &Media::file() { + return content.match([](Photo &data) -> File& { + return data.image.file; + }, [](Document &data) -> File& { + return data.file; + }, [](base::none_type &) -> File& { + static File result; + return result; + }); +} + +const File &Media::file() const { + return content.match([](const Photo &data) -> const File& { + return data.image.file; + }, [](const Document &data) -> const File& { + return data.file; + }, [](const base::none_type &) -> const File& { + static const File result; + return result; + }); +} + +Media ParseMedia( + const MTPMessageMedia &data, + const QString &folder, + TimeId date) { + Expects(folder.isEmpty() || folder.endsWith(QChar('/'))); + + auto result = Media(); + data.match([&](const MTPDmessageMediaPhoto &data) { + result.content = data.has_photo() + ? ParsePhoto( + data.vphoto, + folder + "Photos/" + PreparePhotoFileName(date)) + : Photo(); + if (data.has_ttl_seconds()) { + result.ttl = data.vttl_seconds.v; + } + }, [&](const MTPDmessageMediaGeo &data) { + }, [&](const MTPDmessageMediaContact &data) { + }, [&](const MTPDmessageMediaUnsupported &data) { + }, [&](const MTPDmessageMediaDocument &data) { + result.content = data.has_document() + ? ParseDocument( + data.vdocument, + folder + "Files/", + date) + : Document(); + }, [&](const MTPDmessageMediaWebPage &data) { + // Ignore web pages. + }, [&](const MTPDmessageMediaVenue &data) { + }, [&](const MTPDmessageMediaGame &data) { + }, [&](const MTPDmessageMediaInvoice &data) { + }, [&](const MTPDmessageMediaGeoLive &data) { + }, [](const MTPDmessageMediaEmpty &data) {}); + return result; +} + +ServiceAction ParseServiceAction( + const MTPMessageAction &data, + const QString &mediaFolder, + TimeId date) { + auto result = ServiceAction(); + data.match([&](const MTPDmessageActionChatCreate &data) { + }, [&](const MTPDmessageActionChatEditTitle &data) { + }, [&](const MTPDmessageActionChatEditPhoto &data) { + }, [&](const MTPDmessageActionChatDeletePhoto &data) { + }, [&](const MTPDmessageActionChatAddUser &data) { + }, [&](const MTPDmessageActionChatDeleteUser &data) { + }, [&](const MTPDmessageActionChatJoinedByLink &data) { + }, [&](const MTPDmessageActionChannelCreate &data) { + }, [&](const MTPDmessageActionChatMigrateTo &data) { + }, [&](const MTPDmessageActionChannelMigrateFrom &data) { + }, [&](const MTPDmessageActionPinMessage &data) { + }, [&](const MTPDmessageActionHistoryClear &data) { + }, [&](const MTPDmessageActionGameScore &data) { + }, [&](const MTPDmessageActionPaymentSentMe &data) { + }, [&](const MTPDmessageActionPaymentSent &data) { + }, [&](const MTPDmessageActionPhoneCall &data) { + }, [&](const MTPDmessageActionScreenshotTaken &data) { + }, [&](const MTPDmessageActionCustomAction &data) { + }, [&](const MTPDmessageActionBotAllowed &data) { + }, [&](const MTPDmessageActionSecureValuesSentMe &data) { + }, [&](const MTPDmessageActionSecureValuesSent &data) { + }, [](const MTPDmessageActionEmpty &data) {}); + return result; +} + +Message ParseMessage(const MTPMessage &data, const QString &mediaFolder) { auto result = Message(); - data.visit([&](const MTPDmessage &data) { + data.match([&](const MTPDmessage &data) { result.id = data.vid.v; - result.date = data.vdate.v; + const auto date = result.date = data.vdate.v; + if (data.has_edit_date()) { + result.edited = data.vedit_date.v; + } + if (data.has_from_id()) { + result.fromId = data.vfrom_id.v; + } + if (data.has_reply_to_msg_id()) { + result.replyToMsgId = data.vreply_to_msg_id.v; + } + if (data.has_via_bot_id()) { + result.viaBotId = data.vvia_bot_id.v; + } + if (data.has_media()) { + result.media = ParseMedia(data.vmedia, mediaFolder, date); + } result.text = ParseString(data.vmessage); }, [&](const MTPDmessageService &data) { result.id = data.vid.v; - result.date = data.vdate.v; + const auto date = result.date = data.vdate.v; + result.action = ParseServiceAction(data.vaction, mediaFolder, date); + if (data.has_from_id()) { + result.fromId = data.vfrom_id.v; + } + if (data.has_reply_to_msg_id()) { + result.replyToMsgId = data.vreply_to_msg_id.v; + } }, [&](const MTPDmessageEmpty &data) { result.id = data.vid.v; }); @@ -309,10 +567,11 @@ Message ParseMessage(const MTPMessage &data) { } std::map ParseMessagesList( - const MTPVector &data) { + const MTPVector &data, + const QString &mediaFolder) { auto result = std::map(); for (const auto &message : data.v) { - auto parsed = ParseMessage(message); + auto parsed = ParseMessage(message, mediaFolder); result.emplace(parsed.id, std::move(parsed)); } return result; @@ -397,9 +656,10 @@ SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) { DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) { auto result = DialogsInfo(); - data.visit([&](const auto &data) { // MTPDmessages_dialogs &data) { + const auto folder = QString(); + data.match([&](const auto &data) { // MTPDmessages_dialogs &data) { const auto peers = ParsePeersLists(data.vusers, data.vchats); - const auto messages = ParseMessagesList(data.vmessages); + const auto messages = ParseMessagesList(data.vmessages, folder); result.list.reserve(result.list.size() + data.vdialogs.v.size()); for (const auto &dialog : data.vdialogs.v) { if (dialog.type() != mtpc_dialog) { @@ -437,12 +697,13 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) { MessagesSlice ParseMessagesSlice( const MTPVector &data, const MTPVector &users, - const MTPVector &chats) { + const MTPVector &chats, + const QString &mediaFolder) { const auto &list = data.v; auto result = MessagesSlice(); result.list.reserve(list.size()); for (const auto &message : list) { - result.list.push_back(ParseMessage(message)); + result.list.push_back(ParseMessage(message, mediaFolder)); } ranges::reverse(result.list); result.peers = ParsePeersLists(users, chats); diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 9969185e6e0a0b..4d0c59c431abf4 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -59,13 +59,40 @@ struct File { QString relativePath; }; +struct Image { + int width = 0; + int height = 0; + File file; +}; + struct Photo { uint64 id = 0; TimeId date = 0; + Image image; +}; + +struct Document { + uint64 id = 0; + TimeId date = 0; + + File file; + + Utf8String name; + Utf8String mime; int width = 0; int height = 0; - File image; + + Utf8String stickerEmoji; + Utf8String songPerformer; + Utf8String songTitle; + int duration = 0; + + bool isAnimated = false; + bool isVideoMessage = false; + bool isVoiceMessage = false; + bool isVideoFile = false; + bool isAudioFile = false; }; struct UserpicsSlice { @@ -148,18 +175,45 @@ struct SessionsList { SessionsList ParseSessionsList(const MTPaccount_Authorizations &data); +struct Media { + base::optional_variant content; + TimeId ttl = 0; + + File &file(); + const File &file() const; +}; + +Media ParseMedia( + const MTPMessageMedia &data, + const QString &folder, + TimeId date); + +struct ServiceAction { + base::optional_variant<> data; +}; + +ServiceAction ParseServiceAction( + const MTPMessageAction &data, + const QString &mediaFolder, + TimeId date); + struct Message { int32 id = 0; TimeId date = 0; - + TimeId edited = 0; + int32 fromId = 0; + int32 viaBotId = 0; + int32 replyToMsgId = 0; Utf8String text; - File mediaFile; + Media media; + ServiceAction action; }; -Message ParseMessage(const MTPMessage &data); +Message ParseMessage(const MTPMessage &data, const QString &mediaFolder); std::map ParseMessagesList( - const MTPVector &data); + const MTPVector &data, + const QString &mediaFolder); struct DialogInfo { enum class Type { @@ -175,6 +229,9 @@ struct DialogInfo { MTPInputPeer input; int32 topMessageId = 0; TimeId topMessageDate = 0; + + QString relativePath; + }; struct DialogsInfo { @@ -191,7 +248,8 @@ struct MessagesSlice { MessagesSlice ParseMessagesSlice( const MTPVector &data, const MTPVector &users, - const MTPVector &chats); + const MTPVector &chats, + const QString &mediaFolder); Utf8String FormatPhoneNumber(const Utf8String &phoneNumber); diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index c50250fb72259c..1c873754da8f01 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -24,6 +24,11 @@ constexpr auto kFileNextRequestDelay = TimeMs(20); constexpr auto kChatsSliceLimit = 100; constexpr auto kMessagesSliceLimit = 100; +bool WillLoadFile(const Data::File &file) { + return file.relativePath.isEmpty() + && (!file.content.isEmpty() || file.location.dcId != 0); +} + } // namespace struct ApiWrap::UserpicsProcess { @@ -79,7 +84,7 @@ struct ApiWrap::DialogsProcess { struct ApiWrap::DialogsProcess::Single { Single(const Data::DialogInfo &info); - MTPInputPeer peer; + Data::DialogInfo info; int32 offsetId = 1; base::optional slice; @@ -92,7 +97,7 @@ ApiWrap::FileProcess::FileProcess(const QString &path) : file(path) { } ApiWrap::DialogsProcess::Single::Single(const Data::DialogInfo &info) -: peer(info.input) { +: info(info) { } template @@ -166,7 +171,7 @@ void ApiWrap::requestUserpics( _userpicsProcess->start([&] { auto info = Data::UserpicsInfo(); - result.visit([&](const MTPDphotos_photos &data) { + result.match([&](const MTPDphotos_photos &data) { info.count = data.vphotos.v.size(); }, [&](const MTPDphotos_photosSlice &data) { info.count = data.vcount.v; @@ -181,7 +186,7 @@ void ApiWrap::requestUserpics( void ApiWrap::handleUserpicsSlice(const MTPphotos_Photos &result) { Expects(_userpicsProcess != nullptr); - result.visit([&](const auto &data) { + result.match([&](const auto &data) { if constexpr (MTPDphotos_photos::Is()) { _userpicsProcess->lastSlice = true; } @@ -206,12 +211,18 @@ void ApiWrap::loadNextUserpic() { Expects(_userpicsProcess->slice.has_value()); const auto &list = _userpicsProcess->slice->list; - ++_userpicsProcess->fileIndex; - if (_userpicsProcess->fileIndex < list.size()) { - loadFile( - list[_userpicsProcess->fileIndex].image, - [=](const QString &path) { loadUserpicDone(path); }); - return; + while (true) { + const auto index = ++_userpicsProcess->fileIndex; + if (index >= list.size()) { + break; + } + const auto &file = list[index].image.file; + if (WillLoadFile(file)) { + loadFile( + file, + [=](const QString &path) { loadUserpicDone(path); }); + return; + } } const auto lastUserpicId = list.empty() ? base::none @@ -243,7 +254,8 @@ void ApiWrap::loadUserpicDone(const QString &relativePath) { < _userpicsProcess->slice->list.size())); const auto index = _userpicsProcess->fileIndex; - _userpicsProcess->slice->list[index].image.relativePath = relativePath; + auto &file = _userpicsProcess->slice->list[index].image.file; + file.relativePath = relativePath; loadNextUserpic(); } @@ -364,16 +376,30 @@ void ApiWrap::finishDialogsList() { Expects(_dialogsProcess != nullptr); ranges::reverse(_dialogsProcess->info.list); + fillDialogsPaths(); + _dialogsProcess->start(_dialogsProcess->info); requestNextDialog(); } +void ApiWrap::fillDialogsPaths() { + Expects(_dialogsProcess != nullptr); + + auto &list = _dialogsProcess->info.list; + const auto digits = Data::NumberToString(list.size() - 1).size(); + auto index = 0; + for (auto &dialog : list) { + const auto number = Data::NumberToString(++index, digits, '0'); + dialog.relativePath = "Chats/chat_" + number + '/'; + } +} + void ApiWrap::requestNextDialog() { Expects(_dialogsProcess != nullptr); Expects(_dialogsProcess->single == nullptr); const auto index = ++_dialogsProcess->singleIndex; - if (index < 3) {// _dialogsProcess->info.list.size()) { + if (index < 11) {// _dialogsProcess->info.list.size()) { const auto &one = _dialogsProcess->info.list[index]; _dialogsProcess->single = std::make_unique(one); _dialogsProcess->startOne(one); @@ -389,7 +415,7 @@ void ApiWrap::requestMessagesSlice() { const auto process = _dialogsProcess->single.get(); mainRequest(MTPmessages_GetHistory( - process->peer, + process->info.input, MTP_int(process->offsetId), MTP_int(0), // offset_date MTP_int(-kMessagesSliceLimit), @@ -402,7 +428,7 @@ void ApiWrap::requestMessagesSlice() { Expects(_dialogsProcess->single != nullptr); const auto process = _dialogsProcess->single.get(); - result.visit([&](const MTPDmessages_messagesNotModified &data) { + result.match([&](const MTPDmessages_messagesNotModified &data) { error("Unexpected messagesNotModified received."); }, [&](const auto &data) { if constexpr (MTPDmessages_messages::Is()) { @@ -411,7 +437,8 @@ void ApiWrap::requestMessagesSlice() { loadMessagesFiles(Data::ParseMessagesSlice( data.vmessages, data.vusers, - data.vchats)); + data.vchats, + process->info.relativePath)); }); }).send(); } @@ -438,12 +465,18 @@ void ApiWrap::loadNextMessageFile() { const auto process = _dialogsProcess->single.get(); const auto &list = process->slice->list; - ++process->fileIndex; - if (process->fileIndex < list.size()) { - loadFile( - list[process->fileIndex].mediaFile, - [=](const QString &path) { loadMessageFileDone(path); }); - return; + while (true) { + const auto index = ++process->fileIndex; + if (index >= list.size()) { + break; + } + const auto &file = list[index].media.file(); + if (WillLoadFile(file)) { + loadFile( + file, + [=](const QString &path) { loadMessageFileDone(path); }); + return; + } } _dialogsProcess->sliceOne(*base::take(process->slice)); @@ -468,7 +501,7 @@ void ApiWrap::loadMessageFileDone(const QString &relativePath) { const auto process = _dialogsProcess->single.get(); const auto index = process->fileIndex; - process->slice->list[index].mediaFile.relativePath = relativePath; + process->slice->list[index].media.file().relativePath = relativePath; loadNextMessageFile(); } @@ -493,14 +526,7 @@ void ApiWrap::finishDialogs() { void ApiWrap::loadFile(const Data::File &file, FnMut done) { Expects(_fileProcess == nullptr); Expects(_settings != nullptr); - - if (!file.relativePath.isEmpty()) { - done(file.relativePath); - return; - } else if (file.content.isEmpty() && !file.location.dcId) { - done(QString()); - return; - } + Expects(WillLoadFile(file)); using namespace Output; const auto relativePath = File::PrepareRelativePath( diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index d99e57c244a57c..af4380d475a87c 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -63,6 +63,7 @@ class ApiWrap { void requestDialogsSlice(); void appendDialogsSlice(Data::DialogsInfo &&info); void finishDialogsList(); + void fillDialogsPaths(); void requestNextDialog(); void requestMessagesSlice(); diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp index 0a817facb85a02..03d883389590fb 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.cpp +++ b/Telegram/SourceFiles/export/output/export_output_text.cpp @@ -131,10 +131,10 @@ bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) { lines.append("(deleted photo)"); } else { lines.append(Data::FormatDateTime(userpic.date)).append(" - "); - if (userpic.image.relativePath.isEmpty()) { + if (userpic.image.file.relativePath.isEmpty()) { lines.append("(file unavailable)"); } else { - lines.append(userpic.image.relativePath.toUtf8()); + lines.append(userpic.image.file.relativePath.toUtf8()); } } lines.append(kLineBreak); @@ -259,18 +259,16 @@ bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) { } Unexpected("Dialog type in TypeString."); }; - const auto digits = Data::NumberToString(data.list.size() - 1).size(); const auto file = fileWithRelativePath("chats.txt"); auto list = std::vector(); list.reserve(data.list.size()); auto index = 0; for (const auto &dialog : data.list) { - const auto number = Data::NumberToString(++index, digits, '0'); - const auto path = "Chats/chat_" + number + ".txt"; + const auto path = dialog.relativePath + "messages.txt"; list.push_back(SerializeKeyValue({ { "Name", NameString(dialog.name, dialog.type) }, { "Type", TypeString(dialog.type) }, - { "Content", path } + { "Content", path.toUtf8() } })); } const auto full = JoinList(kLineBreak, list); @@ -291,7 +289,7 @@ bool TextWriter::writeDialogStart(const Data::DialogInfo &data) { const auto digits = Data::NumberToString(_dialogsCount - 1).size(); const auto number = Data::NumberToString(++_dialogIndex, digits, '0'); - _dialog = fileWithRelativePath("Chats/chat_" + number + ".txt"); + _dialog = fileWithRelativePath(data.relativePath + "messages.txt"); return true; } diff --git a/Telegram/SourceFiles/mtproto/core_types.h b/Telegram/SourceFiles/mtproto/core_types.h index 4b3587eaeb0b6f..7325393c5cf93e 100644 --- a/Telegram/SourceFiles/mtproto/core_types.h +++ b/Telegram/SourceFiles/mtproto/core_types.h @@ -13,6 +13,7 @@ For license and copyright information please follow this link: #include #include #include "core/basic_types.h" +#include "base/match_method.h" #include "base/flags.h" #include "base/bytes.h" #include "base/algorithm.h" @@ -227,19 +228,8 @@ class TypeDataOwner { template const DataType &queryData() const { Expects(_data != nullptr); - return static_cast(*_data); - } - template - static decltype(auto) VisitData( - const Data &data, - Callback &&callback, - Callbacks &&...callbacks) { - if constexpr (rpl::details::is_callable_plain_v) { - return std::forward(callback)(data); - } else { - return VisitData(data, std::forward(callbacks)...); - } + return static_cast(*_data); } private: diff --git a/Telegram/SourceFiles/rpl/combine.h b/Telegram/SourceFiles/rpl/combine.h index 97571f58265553..3b594618214c7c 100644 --- a/Telegram/SourceFiles/rpl/combine.h +++ b/Telegram/SourceFiles/rpl/combine.h @@ -8,6 +8,7 @@ For license and copyright information please follow this link: #pragma once #include "base/optional.h" +#include "base/variant.h" #include #include #include @@ -121,7 +122,7 @@ template < class combine_implementation_helper...> { public: using CombinedValue = std::tuple; - using CombinedError = normalized_variant_t; + using CombinedError = base::normalized_variant_t; combine_implementation_helper( producer &&...producers) @@ -154,7 +155,7 @@ template < inline auto combine_implementation( producer &&...producers) { using CombinedValue = std::tuple; - using CombinedError = normalized_variant_t; + using CombinedError = base::normalized_variant_t; return make_producer( make_combine_implementation_helper(std::move(producers)...)); diff --git a/Telegram/SourceFiles/rpl/details/type_list.h b/Telegram/SourceFiles/rpl/details/type_list.h index 9a32c9ae3d98c4..31a381de5b837f 100644 --- a/Telegram/SourceFiles/rpl/details/type_list.h +++ b/Telegram/SourceFiles/rpl/details/type_list.h @@ -8,7 +8,6 @@ For license and copyright information please follow this link: #pragma once #include -#include "base/variant.h" namespace rpl { namespace details { @@ -177,20 +176,5 @@ struct extract_to, To> { }; } // namespace type_list - -template -struct normalized_variant { - using list = type_list::list; - using distinct = type_list::distinct_t; - using type = std::conditional_t< - type_list::size_v == 1, - type_list::get_t<0, distinct>, - type_list::extract_to_t>; -}; - -template -using normalized_variant_t - = typename normalized_variant::type; - } // namespace details } // namespace rpl diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index 08fd6b79648b13..beeca2b68799b9 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -8,6 +8,7 @@ <(src_loc)/base/flat_set.h <(src_loc)/base/functors.h <(src_loc)/base/index_based_iterator.h +<(src_loc)/base/match_method.h <(src_loc)/base/observer.cpp <(src_loc)/base/observer.h <(src_loc)/base/ordered_set.h