Export chat messages photos and documents.

Also rename .visit() with .match() in MTP types.
Also add base::match_method() and base::match() for base::variant.
Also add base::match() and .match() for base::optional_variant.
This commit is contained in:
John Preston 2018-06-13 20:10:12 +03:00
parent 0e9793b845
commit 83786ddeaf
13 changed files with 560 additions and 127 deletions

View File

@ -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 <rpl/details/callable.h>
namespace base {
template <typename Data, typename Method, typename ...Methods>
inline decltype(auto) match_method(
Data &&data,
Method &&method,
Methods &&...methods) {
if constexpr (rpl::details::is_callable_plain_v<Method, Data&&>) {
return std::forward<Method>(method)(std::forward<Data>(data));
} else {
return match_method(
std::forward<Data>(data),
std::forward<Methods>(methods)...);
}
}
} // namespace base

View File

@ -109,6 +109,15 @@ public:
return _impl.template get_unchecked<T>();
}
template <typename ...Methods>
decltype(auto) match(Methods &&...methods) {
return base::match(_impl, std::forward<Methods>(methods)...);
}
template <typename ...Methods>
decltype(auto) match(Methods &&...methods) const {
return base::match(_impl, std::forward<Methods>(methods)...);
}
private:
variant<none_type, Types...> _impl;
@ -124,6 +133,20 @@ inline const T *get_if(const optional_variant<Types...> *v) {
return (v && v->template is<T>()) ? &v->template get_unchecked<T>() : nullptr;
}
template <typename ...Types, typename ...Methods>
inline decltype(auto) match(
optional_variant<Types...> &value,
Methods &&...methods) {
return value.match(std::forward<Methods>(methods)...);
}
template <typename ...Types, typename ...Methods>
inline decltype(auto) match(
const optional_variant<Types...> &value,
Methods &&...methods) {
return value.match(std::forward<Methods>(methods)...);
}
template <typename Type>
class optional;

View File

@ -8,6 +8,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include <mapbox/variant.hpp>
#include <rpl/details/type_list.h>
#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<Types...> *v) {
return (v && v->template is<T>()) ? &v->template get_unchecked<T>() : nullptr;
}
// Simplified visit
template <typename Method, typename... Types>
inline auto visit(Method &&method, const variant<Types...> &value) {
return value.match(std::forward<Method>(method));
namespace type_list = rpl::details::type_list;
template <typename ...Types>
struct normalized_variant {
using list = type_list::list<Types...>;
using distinct = type_list::distinct_t<list>;
using type = std::conditional_t<
type_list::size_v<distinct> == 1,
type_list::get_t<0, distinct>,
type_list::extract_to_t<distinct, base::variant>>;
};
template <typename ...Types>
using normalized_variant_t
= typename normalized_variant<Types...>::type;
template <typename TypeList, typename Variant, typename ...Methods>
struct match_helper;
template <
typename Type,
typename ...Types,
typename Variant,
typename ...Methods>
struct match_helper<type_list::list<Type, Types...>, Variant, Methods...> {
static decltype(auto) call(Variant &value, Methods &&...methods) {
if (const auto v = get_if<Type>(&value)) {
return match_method(
*v,
std::forward<Methods>(methods)...);
}
return match_helper<
type_list::list<Types...>,
Variant,
Methods...>::call(
value,
std::forward<Methods>(methods)...);
}
};
template <
typename Type,
typename Variant,
typename ...Methods>
struct match_helper<type_list::list<Type>, Variant, Methods...> {
static decltype(auto) call(Variant &value, Methods &&...methods) {
if (const auto v = get_if<Type>(&value)) {
return match_method(
*v,
std::forward<Methods>(methods)...);
}
Unexpected("Valueless variant in base::match().");
}
};
template <typename ...Types, typename ...Methods>
inline decltype(auto) match(
variant<Types...> &value,
Methods &&...methods) {
return match_helper<
type_list::list<Types...>,
variant<Types...>,
Methods...>::call(value, std::forward<Methods>(methods)...);
}
template <typename Method, typename... Types>
inline auto visit(Method &&method, variant<Types...> &value) {
return value.match(std::forward<Method>(method));
}
template <typename Method, typename... Types>
inline auto visit(Method &&method, variant<Types...> &&value) {
return value.match(std::forward<Method>(method));
template <typename ...Types, typename ...Methods>
inline decltype(auto) match(
const variant<Types...> &value,
Methods &&...methods) {
return match_helper<
type_list::list<Types...>,
const variant<Types...>,
Methods...>::call(value, std::forward<Methods>(methods)...);
}
} // namespace base

View File

@ -635,7 +635,7 @@ for restype in typesList:
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>(callback), std::forward<Callbacks>(callbacks)...);\n';
visitor += '\tcase mtpc_' + name + ': return base::match_method(c_' + name + '(), std::forward<Method>(method), std::forward<Methods>(methods)...);\n';
forwards += 'class MTPD' + name + ';\n'; # data class forward declaration
if (len(prms) > len(trivialConditions)):
@ -771,14 +771,14 @@ for restype in typesList:
typesText += getters;
if (withType):
typesText += '\n';
typesText += '\ttemplate <typename Callback, typename ...Callbacks>\n';
typesText += '\tdecltype(auto) visit(Callback &&callback, Callbacks &&...callbacks) const;\n';
visitorMethods += 'template <typename Callback, typename ...Callbacks>\n';
visitorMethods += 'decltype(auto) MTP' + restype + '::visit(Callback &&callback, Callbacks &&...callbacks) const {\n';
typesText += '\ttemplate <typename Method, typename ...Methods>\n';
typesText += '\tdecltype(auto) match(Method &&method, Methods &&...methods) const;\n';
visitorMethods += 'template <typename Method, typename ...Methods>\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

View File

@ -7,7 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "export/data/export_data_types.h"
#include "core/mime_type.h"
#include <QtCore/QDateTime>
#include <QtCore/QRegularExpression>
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<MTPPhotoSize> &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) {
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.vsize.v;
result.content = QByteArray();
result.width = data.vw.v;
result.height = data.vh.v;
result.file.location = ParseLocation(data.vlocation);
if constexpr (MTPDphotoCachedSize::Is<decltype(data)>()) {
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 MTPDphotoCachedSize &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;
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<MTPDocumentAttribute> &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<MTPPhoto> &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<MTPPhoto> &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<int32, User> ParseUsersList(const MTPVector<MTPUser> &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<PeerId, Peer> 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<int32, Message> ParseMessagesList(
const MTPVector<MTPMessage> &data) {
const MTPVector<MTPMessage> &data,
const QString &mediaFolder) {
auto result = std::map<int32, Message>();
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<MTPMessage> &data,
const MTPVector<MTPUser> &users,
const MTPVector<MTPChat> &chats) {
const MTPVector<MTPChat> &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);

View File

@ -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<Photo, Document> 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<int32, Message> ParseMessagesList(
const MTPVector<MTPMessage> &data);
const MTPVector<MTPMessage> &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<MTPMessage> &data,
const MTPVector<MTPUser> &users,
const MTPVector<MTPChat> &chats);
const MTPVector<MTPChat> &chats,
const QString &mediaFolder);
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber);

View File

@ -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<Data::MessagesSlice> 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 <typename Request>
@ -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<decltype(data)>()) {
_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<DialogsProcess::Single>(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<decltype(data)>()) {
@ -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<void(QString)> 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(

View File

@ -63,6 +63,7 @@ private:
void requestDialogsSlice();
void appendDialogsSlice(Data::DialogsInfo &&info);
void finishDialogsList();
void fillDialogsPaths();
void requestNextDialog();
void requestMessagesSlice();

View File

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

View File

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QByteArray>
#include <rpl/details/callable.h>
#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 @@ protected:
template <typename DataType>
const DataType &queryData() const {
Expects(_data != nullptr);
return static_cast<const DataType &>(*_data);
}
template <typename Data, typename Callback, typename ...Callbacks>
static decltype(auto) VisitData(
const Data &data,
Callback &&callback,
Callbacks &&...callbacks) {
if constexpr (rpl::details::is_callable_plain_v<Callback, Data>) {
return std::forward<Callback>(callback)(data);
} else {
return VisitData(data, std::forward<Callbacks>(callbacks)...);
}
return static_cast<const DataType &>(*_data);
}
private:

View File

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "base/optional.h"
#include "base/variant.h"
#include <rpl/map.h>
#include <rpl/producer.h>
#include <rpl/details/type_list.h>
@ -121,7 +122,7 @@ template <
class combine_implementation_helper<producer<Values, Errors, Generators>...> {
public:
using CombinedValue = std::tuple<Values...>;
using CombinedError = normalized_variant_t<Errors...>;
using CombinedError = base::normalized_variant_t<Errors...>;
combine_implementation_helper(
producer<Values, Errors, Generators> &&...producers)
@ -154,7 +155,7 @@ template <
inline auto combine_implementation(
producer<Values, Errors, Generators> &&...producers) {
using CombinedValue = std::tuple<Values...>;
using CombinedError = normalized_variant_t<Errors...>;
using CombinedError = base::normalized_variant_t<Errors...>;
return make_producer<CombinedValue, CombinedError>(
make_combine_implementation_helper(std::move(producers)...));

View File

@ -8,7 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include <type_traits>
#include "base/variant.h"
namespace rpl {
namespace details {
@ -177,20 +176,5 @@ struct extract_to<list<Types...>, To> {
};
} // namespace type_list
template <typename ...Types>
struct normalized_variant {
using list = type_list::list<Types...>;
using distinct = type_list::distinct_t<list>;
using type = std::conditional_t<
type_list::size_v<distinct> == 1,
type_list::get_t<0, distinct>,
type_list::extract_to_t<distinct, base::variant>>;
};
template <typename ...Types>
using normalized_variant_t
= typename normalized_variant<Types...>::type;
} // namespace details
} // namespace rpl

View File

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