diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 50fa21b0dfac9..8e12096e2bba3 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -7,12 +7,44 @@ For license and copyright information please follow this link: */ #include "export/data/export_data_types.h" +#include + namespace App { // Hackish.. QString formatPhone(QString phone); } // namespace App namespace Export { namespace Data { +namespace { + +constexpr auto kUserPeerIdShift = (1ULL << 32); +constexpr auto kChatPeerIdShift = (2ULL << 32); + +} // namespace + +PeerId UserPeerId(int32 userId) { + return kUserPeerIdShift | uint32(userId); +} + +PeerId ChatPeerId(int32 chatId) { + return kChatPeerIdShift | uint32(chatId); +} + +int32 BarePeerId(PeerId peerId) { + return int32(peerId & 0xFFFFFFFFULL); +} + +PeerId ParsePeerId(const MTPPeer &data) { + switch (data.type()) { + case mtpc_peerUser: + return UserPeerId(data.c_peerUser().vuser_id.v); + case mtpc_peerChat: + return ChatPeerId(data.c_peerChat().vchat_id.v); + case mtpc_peerChannel: + return ChatPeerId(data.c_peerChannel().vchannel_id.v); + } + Unexpected("Type in ParsePeerId."); +} Utf8String ParseString(const MTPstring &data) { return data.v; @@ -85,7 +117,7 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) { case mtpc_photo: { const auto &photo = data.c_photo(); result.id = photo.vid.v; - result.date = QDateTime::fromTime_t(photo.vdate.v); + result.date = photo.vdate.v; result.image = ParseMaxImage(photo.vsizes, suggestedPath); } break; @@ -100,18 +132,19 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) { } Utf8String FormatDateTime( - const QDateTime &date, + TimeId date, QChar dateSeparator, QChar timeSeparator, QChar separator) { + const auto value = QDateTime::fromTime_t(date); return (QString("%1") + dateSeparator + "%2" + dateSeparator + "%3" + separator + "%4" + timeSeparator + "%5" + timeSeparator + "%6" - ).arg(date.date().year() - ).arg(date.date().month(), 2, 10, QChar('0') - ).arg(date.date().day(), 2, 10, QChar('0') - ).arg(date.time().hour(), 2, 10, QChar('0') - ).arg(date.time().minute(), 2, 10, QChar('0') - ).arg(date.time().second(), 2, 10, QChar('0') + ).arg(value.date().year() + ).arg(value.date().month(), 2, 10, QChar('0') + ).arg(value.date().day(), 2, 10, QChar('0') + ).arg(value.time().hour(), 2, 10, QChar('0') + ).arg(value.time().minute(), 2, 10, QChar('0') + ).arg(value.time().second(), 2, 10, QChar('0') ).toUtf8(); } @@ -149,11 +182,17 @@ User ParseUser(const MTPUser &data) { if (fields.has_username()) { result.username = ParseString(fields.vusername); } + if (fields.has_access_hash()) { + result.input = MTP_inputUser(fields.vid, fields.vaccess_hash); + } else { + result.input = MTP_inputUserEmpty(); + } } break; case mtpc_userEmpty: { const auto &fields = data.c_userEmpty(); result.id = fields.vid.v; + result.input = MTP_inputUserEmpty(); } break; default: Unexpected("Type in ParseUser."); @@ -161,8 +200,8 @@ User ParseUser(const MTPUser &data) { return result; } -std::map ParseUsersList(const MTPVector &data) { - auto result = std::map(); +std::map ParseUsersList(const MTPVector &data) { + auto result = std::map(); for (const auto &user : data.v) { auto parsed = ParseUser(user); result.emplace(parsed.id, std::move(parsed)); @@ -170,6 +209,152 @@ std::map ParseUsersList(const MTPVector &data) { return result; } +Chat ParseChat(const MTPChat &data) { + auto result = Chat(); + switch (data.type()) { + case mtpc_chat: { + const auto &fields = data.c_chat(); + result.id = fields.vid.v; + result.title = ParseString(fields.vtitle); + result.input = MTP_inputPeerChat(MTP_int(result.id)); + } break; + + case mtpc_chatEmpty: { + const auto &fields = data.c_chatEmpty(); + result.id = fields.vid.v; + result.input = MTP_inputPeerChat(MTP_int(result.id)); + } break; + + case mtpc_chatForbidden: { + const auto &fields = data.c_chatForbidden(); + result.id = fields.vid.v; + result.title = ParseString(fields.vtitle); + result.input = MTP_inputPeerChat(MTP_int(result.id)); + } break; + + case mtpc_channel: { + const auto &fields = data.c_channel(); + result.id = fields.vid.v; + result.broadcast = fields.is_broadcast(); + result.title = ParseString(fields.vtitle); + if (fields.has_username()) { + result.username = ParseString(fields.vusername); + } + result.input = MTP_inputPeerChannel( + MTP_int(result.id), + fields.vaccess_hash); + } break; + + case mtpc_channelForbidden: { + const auto &fields = data.c_channelForbidden(); + result.id = fields.vid.v; + result.broadcast = fields.is_broadcast(); + result.title = ParseString(fields.vtitle); + result.input = MTP_inputPeerChannel( + MTP_int(result.id), + fields.vaccess_hash); + } break; + + default: Unexpected("Type in ParseChat."); + } + return result; +} + +std::map ParseChatsList(const MTPVector &data) { + auto result = std::map(); + for (const auto &chat : data.v) { + auto parsed = ParseChat(chat); + result.emplace(parsed.id, std::move(parsed)); + } + return result; +} + +const User *Peer::user() const { + return base::get_if(&data); +} +const Chat *Peer::chat() const { + return base::get_if(&data); +} + +PeerId Peer::id() const { + if (const auto user = this->user()) { + return UserPeerId(user->id); + } else if (const auto chat = this->chat()) { + return ChatPeerId(chat->id); + } + Unexpected("Variant in Peer::id."); +} + +Utf8String Peer::name() const { + if (const auto user = this->user()) { + return user->firstName + ' ' + user->lastName; + } else if (const auto chat = this->chat()) { + return chat->title; + } + Unexpected("Variant in Peer::id."); +} + +MTPInputPeer Peer::input() const { + if (const auto user = this->user()) { + if (user->input.type() == mtpc_inputUser) { + const auto &input = user->input.c_inputUser(); + return MTP_inputPeerUser(input.vuser_id, input.vaccess_hash); + } + return MTP_inputPeerEmpty(); + } else if (const auto chat = this->chat()) { + return chat->input; + } + Unexpected("Variant in Peer::id."); +} + +std::map ParsePeersLists( + const MTPVector &users, + const MTPVector &chats) { + auto result = std::map(); + for (const auto &user : users.v) { + auto parsed = ParseUser(user); + result.emplace(UserPeerId(parsed.id), Peer{ std::move(parsed) }); + } + for (const auto &chat : chats.v) { + auto parsed = ParseChat(chat); + result.emplace(ChatPeerId(parsed.id), Peer{ std::move(parsed) }); + } + return result; +} + +Message ParseMessage(const MTPMessage &data) { + auto result = Message(); + switch (data.type()) { + case mtpc_message: { + const auto &fields = data.c_message(); + result.id = fields.vid.v; + result.date = fields.vdate.v; + } break; + + case mtpc_messageService: { + const auto &fields = data.c_messageService(); + result.id = fields.vid.v; + result.date = fields.vdate.v; + } break; + + case mtpc_messageEmpty: { + const auto &fields = data.c_messageEmpty(); + result.id = fields.vid.v; + } break; + } + return result; +} + +std::map ParseMessagesList( + const MTPVector &data) { + auto result = std::map(); + for (const auto &message : data.v) { + auto parsed = ParseMessage(message); + result.emplace(parsed.id, std::move(parsed)); + } + return result; +} + PersonalInfo ParsePersonalInfo(const MTPUserFull &data) { Expects(data.type() == mtpc_userFull); @@ -188,6 +373,7 @@ ContactsList ParseContactsList(const MTPcontacts_Contacts &data) { auto result = ContactsList(); const auto &contacts = data.c_contacts_contacts(); const auto map = ParseUsersList(contacts.vusers); + result.list.reserve(contacts.vcontacts.v.size()); for (const auto &contact : contacts.vcontacts.v) { const auto userId = contact.c_contact().vuser_id.v; if (const auto i = map.find(userId); i != end(map)) { @@ -226,8 +412,8 @@ Session ParseSession(const MTPAuthorization &data) { result.systemVersion = ParseString(fields.vsystem_version); result.applicationName = ParseString(fields.vapp_name); result.applicationVersion = ParseString(fields.vapp_version); - result.created = QDateTime::fromTime_t(fields.vdate_created.v); - result.lastActive = QDateTime::fromTime_t(fields.vdate_active.v); + result.created = fields.vdate_created.v; + result.lastActive = fields.vdate_active.v; result.ip = ParseString(fields.vip); result.country = ParseString(fields.vcountry); result.region = ParseString(fields.vregion); @@ -239,12 +425,62 @@ SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) { auto result = SessionsList(); const auto &list = data.c_account_authorizations().vauthorizations.v; + result.list.reserve(list.size()); for (const auto &session : list) { result.list.push_back(ParseSession(session)); } return result; } +void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data) { +// const auto process = [&](const MTPDmessages_dialogs &data) { + const auto process = [&](const auto &data) { + const auto peers = ParsePeersLists(data.vusers, data.vchats); + const auto messages = ParseMessagesList(data.vmessages); + to.list.reserve(to.list.size() + data.vdialogs.v.size()); + for (const auto &dialog : data.vdialogs.v) { + if (dialog.type() != mtpc_dialog) { + continue; + } + const auto &fields = dialog.c_dialog(); + + auto info = DialogInfo(); + const auto peerId = ParsePeerId(fields.vpeer); + const auto peerIt = peers.find(peerId); + if (peerIt != end(peers)) { + const auto &peer = peerIt->second; + info.type = peer.user() + ? DialogInfo::Type::Personal + : peer.chat()->broadcast + ? DialogInfo::Type::Channel + : peer.chat()->username.isEmpty() + ? DialogInfo::Type::PrivateGroup + : DialogInfo::Type::PublicGroup; + info.name = peer.name(); + info.input = peer.input(); + } + info.topMessageId = fields.vtop_message.v; + const auto messageIt = messages.find(info.topMessageId); + if (messageIt != end(messages)) { + const auto &message = messageIt->second; + info.topMessageDate = message.date; + } + to.list.push_back(std::move(info)); + } + }; + switch (data.type()) { + case mtpc_messages_dialogs: + process(data.c_messages_dialogs()); + break; + + case mtpc_messages_dialogsSlice: + process(data.c_messages_dialogsSlice()); + break; + + default: Unexpected("Type in AppendParsedChats."); + } +} + Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) { return phoneNumber.isEmpty() ? Utf8String() diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 8ea0ff73942d8..bd5e2191e8993 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -9,8 +9,8 @@ For license and copyright information please follow this link: #include "scheme.h" #include "base/optional.h" +#include "base/variant.h" -#include #include #include #include @@ -18,7 +18,13 @@ For license and copyright information please follow this link: namespace Export { namespace Data { +using TimeId = int32; using Utf8String = QByteArray; +using PeerId = uint64; + +PeerId UserPeerId(int32 userId); +PeerId ChatPeerId(int32 chatId); +int32 BarePeerId(PeerId peerId); Utf8String ParseString(const MTPstring &data); @@ -50,7 +56,7 @@ struct File { struct Photo { uint64 id = 0; - QDateTime date; + TimeId date = 0; int width = 0; int height = 0; @@ -64,15 +70,45 @@ struct UserpicsSlice { UserpicsSlice ParseUserpicsSlice(const MTPVector &data); struct User { - int id = 0; + int32 id = 0; Utf8String firstName; Utf8String lastName; Utf8String phoneNumber; Utf8String username; + + MTPInputUser input; +}; + +User ParseUser(const MTPUser &data); +std::map ParseUsersList(const MTPVector &data); + +struct Chat { + int32 id = 0; + Utf8String title; + Utf8String username; + bool broadcast = false; + + MTPInputPeer input; +}; + +Chat ParseChat(const MTPChat &data); +std::map ParseChatsList(const MTPVector &data); + +struct Peer { + PeerId id() const; + Utf8String name() const; + MTPInputPeer input() const; + + const User *user() const; + const Chat *chat() const; + + base::variant data; + }; -User ParseUser(const MTPUser &user); -std::map ParseUsersList(const MTPVector &data); +std::map ParsePeersLists( + const MTPVector &users, + const MTPVector &chats); struct PersonalInfo { User user; @@ -94,8 +130,8 @@ struct Session { Utf8String systemVersion; Utf8String applicationName; Utf8String applicationVersion; - QDateTime created; - QDateTime lastActive; + TimeId created = 0; + TimeId lastActive = 0; Utf8String ip; Utf8String country; Utf8String region; @@ -107,24 +143,38 @@ struct SessionsList { SessionsList ParseSessionsList(const MTPaccount_Authorizations &data); -struct ChatsInfo { - int count = 0; +struct Message { + int32 id = 0; + TimeId date = 0; + }; -struct ChatInfo { +Message ParseMessage(const MTPMessage &data); +std::map ParseMessagesList( + const MTPVector &data); + +struct DialogInfo { enum class Type { + Unknown, Personal, - Group, + PrivateGroup, + PublicGroup, Channel, }; - Type type = Type::Personal; - QString name; + Type type = Type::Unknown; + Utf8String name; + + MTPInputPeer input; + int32 topMessageId = 0; + TimeId topMessageDate = 0; }; -struct Message { - int id = 0; +struct DialogsInfo { + std::vector list; }; +void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data); + struct MessagesSlice { std::vector list; }; @@ -132,22 +182,10 @@ struct MessagesSlice { Utf8String FormatPhoneNumber(const Utf8String &phoneNumber); Utf8String FormatDateTime( - const QDateTime &date, + TimeId date, QChar dateSeparator = QChar('.'), QChar timeSeparator = QChar(':'), QChar separator = QChar(' ')); -inline Utf8String FormatDateTime( - int32 date, - QChar dateSeparator = QChar('.'), - QChar timeSeparator = QChar(':'), - QChar separator = QChar(' ')) { - return FormatDateTime( - QDateTime::fromTime_t(date), - dateSeparator, - timeSeparator, - separator); -} - } // namespace Data } // namespace Export diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 2ceda3677f1e4..81cb5cf0c830d 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -16,10 +16,11 @@ For license and copyright information please follow this link: namespace Export { namespace { -constexpr auto kUserpicsSliceLimit = 2; +constexpr auto kUserpicsSliceLimit = 100; constexpr auto kFileChunkSize = 128 * 1024; constexpr auto kFileRequestsCount = 2; constexpr auto kFileNextRequestDelay = TimeMs(20); +constexpr auto kChatsSliceLimit = 200; } // namespace @@ -54,6 +55,17 @@ struct ApiWrap::FileProcess { }; +struct ApiWrap::DialogsProcess { + Data::DialogsInfo info; + + FnMut done; + + int32 offsetDate = 0; + int32 offsetId = 0; + MTPInputPeer offsetPeer = MTP_inputPeerEmpty(); + +}; + ApiWrap::FileProcess::FileProcess(const QString &path) : file(path) { } @@ -251,6 +263,49 @@ void ApiWrap::requestSessions(FnMut done) { }).send(); } +void ApiWrap::requestDialogs(FnMut done) { + Expects(_dialogsProcess == nullptr); + + _dialogsProcess = std::make_unique(); + _dialogsProcess->done = std::move(done); + requestDialogsSlice(); +} + +void ApiWrap::requestDialogsSlice() { + Expects(_dialogsProcess != nullptr); + + mainRequest(MTPmessages_GetDialogs( + MTP_flags(0), + MTP_int(_dialogsProcess->offsetDate), + MTP_int(_dialogsProcess->offsetId), + _dialogsProcess->offsetPeer, + MTP_int(kChatsSliceLimit) + )).done([=](const MTPmessages_Dialogs &result) mutable { + const auto finished = [&] { + switch (result.type()) { + case mtpc_messages_dialogs: return true; + case mtpc_messages_dialogsSlice: { + const auto &data = result.c_messages_dialogsSlice(); + return data.vdialogs.v.isEmpty(); + } break; + default: Unexpected("Type in ApiWrap::requestChatsSlice."); + } + }(); + Data::AppendParsedDialogs(_dialogsProcess->info, result); + if (finished || _dialogsProcess->info.list.empty()) { + auto process = base::take(_dialogsProcess); + ranges::reverse(process->info.list); + process->done(std::move(process->info)); + } else { + const auto &last = _dialogsProcess->info.list.back(); + _dialogsProcess->offsetId = last.topMessageId; + _dialogsProcess->offsetDate = last.topMessageDate; + _dialogsProcess->offsetPeer = last.input; + requestDialogsSlice(); + } + }).send(); +} + void ApiWrap::loadFile(const Data::File &file, FnMut done) { Expects(_fileProcess == nullptr); diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index e826ddc91910b..1410291596398 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -18,6 +18,7 @@ struct UserpicsInfo; struct UserpicsSlice; struct ContactsList; struct SessionsList; +struct DialogsInfo; } // namespace Data class ApiWrap { @@ -39,6 +40,8 @@ class ApiWrap { void requestSessions(FnMut done); + void requestDialogs(FnMut done); + ~ApiWrap(); private: @@ -48,6 +51,8 @@ class ApiWrap { void loadUserpicDone(const QString &relativePath); void finishUserpics(); + void requestDialogsSlice(); + void loadFile(const Data::File &file, FnMut done); void loadFilePart(); void filePartDone(int offset, const MTPupload_File &result); @@ -72,6 +77,9 @@ class ApiWrap { struct FileProcess; std::unique_ptr _fileProcess; + struct DialogsProcess; + std::unique_ptr _dialogsProcess; + rpl::event_stream _errors; }; diff --git a/Telegram/SourceFiles/export/export_controller.cpp b/Telegram/SourceFiles/export/export_controller.cpp index b5318239de65c..b6439cb7863cf 100644 --- a/Telegram/SourceFiles/export/export_controller.cpp +++ b/Telegram/SourceFiles/export/export_controller.cpp @@ -46,7 +46,7 @@ class Controller { void exportUserpics(); void exportContacts(); void exportSessions(); - void exportChats(); + void exportDialogs(); bool normalizePath(); @@ -206,12 +206,12 @@ void Controller::fillExportSteps() { if (_settings.types & Type::Sessions) { _steps.push_back(Step::Sessions); } - const auto chatTypes = Type::PersonalChats + const auto dialogTypes = Type::PersonalChats | Type::PrivateGroups | Type::PublicGroups | Type::MyChannels; - if (_settings.types & chatTypes) { - _steps.push_back(Step::Chats); + if (_settings.types & dialogTypes) { + _steps.push_back(Step::Dialogs); } } @@ -230,7 +230,7 @@ void Controller::exportNext() { case Step::Userpics: return exportUserpics(); case Step::Contacts: return exportContacts(); case Step::Sessions: return exportSessions(); - case Step::Chats: return exportChats(); + case Step::Dialogs: return exportDialogs(); } Unexpected("Step in Controller::exportNext."); } @@ -267,8 +267,11 @@ void Controller::exportSessions() { }); } -void Controller::exportChats() { - exportNext(); +void Controller::exportDialogs() { + _api.requestDialogs([=](Data::DialogsInfo &&result) { + _writer->writeDialogsStart(result); + exportNext(); + }); } void Controller::setFinishedState() { diff --git a/Telegram/SourceFiles/export/export_controller.h b/Telegram/SourceFiles/export/export_controller.h index f9281d30301b4..73ab039ce4a5b 100644 --- a/Telegram/SourceFiles/export/export_controller.h +++ b/Telegram/SourceFiles/export/export_controller.h @@ -31,7 +31,7 @@ struct ProcessingState { Userpics, Contacts, Sessions, - Chats, + Dialogs, }; enum class Item { Other, diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.h b/Telegram/SourceFiles/export/output/export_output_abstract.h index c16152d5391aa..9788081d61832 100644 --- a/Telegram/SourceFiles/export/output/export_output_abstract.h +++ b/Telegram/SourceFiles/export/output/export_output_abstract.h @@ -16,8 +16,8 @@ struct UserpicsInfo; struct UserpicsSlice; struct ContactsList; struct SessionsList; -struct ChatsInfo; -struct ChatInfo; +struct DialogsInfo; +struct DialogInfo; struct MessagesSlice; } // namespace Data @@ -43,11 +43,11 @@ class AbstractWriter { virtual bool writeSessionsList(const Data::SessionsList &data) = 0; - virtual bool writeChatsStart(const Data::ChatsInfo &data) = 0; - virtual bool writeChatStart(const Data::ChatInfo &data) = 0; + virtual bool writeDialogsStart(const Data::DialogsInfo &data) = 0; + virtual bool writeDialogStart(const Data::DialogInfo &data) = 0; virtual bool writeMessagesSlice(const Data::MessagesSlice &data) = 0; - virtual bool writeChatEnd() = 0; - virtual bool writeChatsEnd() = 0; + virtual bool writeDialogEnd() = 0; + virtual bool writeDialogsEnd() = 0; virtual bool finish() = 0; diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp index 0a5c99889729a..93f3bdf31ed40 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.cpp +++ b/Telegram/SourceFiles/export/output/export_output_text.cpp @@ -127,7 +127,7 @@ bool TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) { bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) { auto lines = QByteArray(); for (const auto &userpic : data.list) { - if (!userpic.date.isValid()) { + if (!userpic.date) { lines.append("(empty photo)"); } else { lines.append(Data::FormatDateTime(userpic.date)).append(" - "); @@ -153,10 +153,7 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) { return true; } - const auto header = "Contacts " - "(" + Data::NumberToString(data.list.size()) + ")" - + kLineBreak - + kLineBreak; + const auto file = std::make_unique(_folder + "contacts.txt"); auto list = std::vector(); list.reserve(data.list.size()); for (const auto &index : Data::SortedContactsIndices(data)) { @@ -178,15 +175,24 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) { })); } } - const auto full = header + JoinList(kLineBreak, list) + kLineBreak; - return _result->writeBlock(full) == File::Result::Success; -} + const auto full = JoinList(kLineBreak, list); + if (file->writeBlock(full) != File::Result::Success) { + return false; + } -bool TextWriter::writeSessionsList(const Data::SessionsList &data) { - const auto header = "Sessions " - "(" + Data::NumberToString(data.list.size()) + ")" + const auto header = "Contacts " + "(" + Data::NumberToString(data.list.size()) + ") - contacts.txt" + kLineBreak + kLineBreak; + return _result->writeBlock(header) == File::Result::Success; +} + +bool TextWriter::writeSessionsList(const Data::SessionsList &data) { + if (data.list.empty()) { + return true; + } + + const auto file = std::make_unique(_folder + "sessions.txt"); auto list = std::vector(); list.reserve(data.list.size()); for (const auto &session : data.list) { @@ -208,15 +214,65 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) { { "Created", Data::FormatDateTime(session.created) }, })); } - const auto full = header + JoinList(kLineBreak, list) + kLineBreak; - return _result->writeBlock(full) == File::Result::Success; + const auto full = JoinList(kLineBreak, list); + if (file->writeBlock(full) != File::Result::Success) { + return false; + } + + const auto header = "Sessions " + "(" + Data::NumberToString(data.list.size()) + ") - sessions.txt" + + kLineBreak + + kLineBreak; + return _result->writeBlock(header) == File::Result::Success; } -bool TextWriter::writeChatsStart(const Data::ChatsInfo &data) { - return true; +bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) { + if (data.list.empty()) { + return true; + } + + using Type = Data::DialogInfo::Type; + const auto TypeString = [](Type type) { + switch (type) { + case Type::Unknown: return "(unknown)"; + case Type::Personal: return "Personal Chat"; + case Type::PrivateGroup: return "Private Group"; + case Type::PublicGroup: return "Public Group"; + case Type::Channel: return "Channel"; + } + Unexpected("Dialog type in TypeString."); + }; + const auto digits = Data::NumberToString(data.list.size() - 1).size(); + const auto file = std::make_unique(_folder + "chats.txt"); + auto list = std::vector(); + list.reserve(data.list.size()); + auto index = 0; + for (const auto &dialog : data.list) { + auto number = Data::NumberToString(++index); + auto path = QByteArray("Chats/chat_"); + for (auto i = number.size(); i < digits; ++i) { + path += '0'; + } + path += number + ".txt"; + list.push_back(SerializeKeyValue({ + { "Name", dialog.name }, + { "Type", TypeString(dialog.type) }, + { "Content", path } + })); + } + const auto full = JoinList(kLineBreak, list); + if (file->writeBlock(full) != File::Result::Success) { + return false; + } + + const auto header = "Chats " + "(" + Data::NumberToString(data.list.size()) + ") - chats.txt" + + kLineBreak + + kLineBreak; + return _result->writeBlock(header) == File::Result::Success; } -bool TextWriter::writeChatStart(const Data::ChatInfo &data) { +bool TextWriter::writeDialogStart(const Data::DialogInfo &data) { return true; } @@ -224,11 +280,11 @@ bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) { return true; } -bool TextWriter::writeChatEnd() { +bool TextWriter::writeDialogEnd() { return true; } -bool TextWriter::writeChatsEnd() { +bool TextWriter::writeDialogsEnd() { return true; } diff --git a/Telegram/SourceFiles/export/output/export_output_text.h b/Telegram/SourceFiles/export/output/export_output_text.h index 8a1083da957d8..cc857c4c1a7c3 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.h +++ b/Telegram/SourceFiles/export/output/export_output_text.h @@ -27,11 +27,11 @@ class TextWriter : public AbstractWriter { bool writeSessionsList(const Data::SessionsList &data) override; - bool writeChatsStart(const Data::ChatsInfo &data) override; - bool writeChatStart(const Data::ChatInfo &data) override; + bool writeDialogsStart(const Data::DialogsInfo &data) override; + bool writeDialogStart(const Data::DialogInfo &data) override; bool writeMessagesSlice(const Data::MessagesSlice &data) override; - bool writeChatEnd() override; - bool writeChatsEnd() override; + bool writeDialogEnd() override; + bool writeDialogsEnd() override; bool finish() override;