From 2b36dd660b3688f1ca20252fe742dd2fcfff8fd5 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 13 Jun 2018 16:12:36 +0300 Subject: [PATCH] Export chat messages text. --- .../export/data/export_data_types.cpp | 62 +++- .../export/data/export_data_types.h | 20 +- .../SourceFiles/export/export_api_wrap.cpp | 265 +++++++++++++++--- Telegram/SourceFiles/export/export_api_wrap.h | 28 +- .../SourceFiles/export/export_controller.cpp | 12 +- Telegram/SourceFiles/export/export_settings.h | 5 + .../export/output/export_output_file.cpp | 4 + .../export/output/export_output_file.h | 2 + .../export/output/export_output_text.cpp | 84 ++++-- .../export/output/export_output_text.h | 8 + 10 files changed, 418 insertions(+), 72 deletions(-) diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index c257c96767763..91e4e606bec0d 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -48,6 +48,19 @@ Utf8String ParseString(const MTPstring &data) { return data.v; } +Utf8String FillLeft(const Utf8String &data, int length, char filler) { + if (length <= data.size()) { + return data; + } + auto result = Utf8String(); + result.reserve(length); + for (auto i = 0, count = length - data.size(); i != count; ++i) { + result.append(filler); + } + result.append(data); + return result; +} + FileLocation ParseLocation(const MTPFileLocation &data) { return data.visit([](const MTPDfileLocation &data) { return FileLocation{ @@ -158,14 +171,13 @@ User ParseUser(const MTPUser &data) { if (data.has_username()) { result.username = ParseString(data.vusername); } - if (data.has_access_hash()) { - result.input = MTP_inputUser(data.vid, data.vaccess_hash); - } else { - result.input = MTP_inputUserEmpty(); - } + const auto access_hash = data.has_access_hash() + ? data.vaccess_hash + : MTP_long(0); + result.input = MTP_inputUser(data.vid, access_hash); }, [&](const MTPDuserEmpty &data) { result.id = data.vid.v; - result.input = MTP_inputUserEmpty(); + result.input = MTP_inputUser(data.vid, MTP_long(0)); }); return result; } @@ -240,7 +252,13 @@ PeerId Peer::id() const { Utf8String Peer::name() const { if (const auto user = this->user()) { - return user->firstName + ' ' + user->lastName; + return user->firstName.isEmpty() + ? (user->lastName.isEmpty() + ? Utf8String() + : user->lastName) + : (user->lastName.isEmpty() + ? user->firstName + : user->firstName + ' ' + user->lastName); } else if (const auto chat = this->chat()) { return chat->title; } @@ -280,6 +298,7 @@ Message ParseMessage(const MTPMessage &data) { data.visit([&](const MTPDmessage &data) { result.id = data.vid.v; result.date = data.vdate.v; + result.text = ParseString(data.vmessage); }, [&](const MTPDmessageService &data) { result.id = data.vid.v; result.date = data.vdate.v; @@ -376,12 +395,12 @@ SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) { return result; } -void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data) { -// const auto process = [&](const MTPDmessages_dialogs &data) { - const auto process = [&](const auto &data) { +DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) { + auto result = DialogsInfo(); + data.visit([&](const auto &data) { // MTPDmessages_dialogs &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()); + result.list.reserve(result.list.size() + data.vdialogs.v.size()); for (const auto &dialog : data.vdialogs.v) { if (dialog.type() != mtpc_dialog) { continue; @@ -409,10 +428,25 @@ void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data) { const auto &message = messageIt->second; info.topMessageDate = message.date; } - to.list.push_back(std::move(info)); + result.list.push_back(std::move(info)); } - }; - data.visit(process); + }); + return result; +} + +MessagesSlice ParseMessagesSlice( + const MTPVector &data, + const MTPVector &users, + const MTPVector &chats) { + const auto &list = data.v; + auto result = MessagesSlice(); + result.list.reserve(list.size()); + for (const auto &message : list) { + result.list.push_back(ParseMessage(message)); + } + ranges::reverse(result.list); + result.peers = ParsePeersLists(users, chats); + return result; } Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) { diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index bd5e2191e8993..9969185e6e0a0 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -28,11 +28,16 @@ int32 BarePeerId(PeerId peerId); Utf8String ParseString(const MTPstring &data); +Utf8String FillLeft(const Utf8String &data, int length, char filler); + template -inline auto NumberToString(Type value) +inline auto NumberToString(Type value, int length = 0, char filler = '0') -> std::enable_if_t, Utf8String> { const auto result = std::to_string(value); - return QByteArray(result.data(), int(result.size())); + return FillLeft( + Utf8String(result.data(), int(result.size())), + length, + filler); } struct UserpicsInfo { @@ -147,6 +152,9 @@ struct Message { int32 id = 0; TimeId date = 0; + Utf8String text; + File mediaFile; + }; Message ParseMessage(const MTPMessage &data); @@ -173,12 +181,18 @@ struct DialogsInfo { std::vector list; }; -void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data); +DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data); struct MessagesSlice { std::vector list; + std::map peers; }; +MessagesSlice ParseMessagesSlice( + const MTPVector &data, + const MTPVector &users, + const MTPVector &chats); + Utf8String FormatPhoneNumber(const Utf8String &phoneNumber); Utf8String FormatDateTime( diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 9f1ff42196075..c50250fb72259 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #include "export/export_api_wrap.h" +#include "export/export_settings.h" #include "export/data/export_data_types.h" #include "export/output/export_output_file.h" #include "mtproto/rpc_sender.h" @@ -20,7 +21,8 @@ constexpr auto kUserpicsSliceLimit = 100; constexpr auto kFileChunkSize = 128 * 1024; constexpr auto kFileRequestsCount = 2; constexpr auto kFileNextRequestDelay = TimeMs(20); -constexpr auto kChatsSliceLimit = 200; +constexpr auto kChatsSliceLimit = 100; +constexpr auto kMessagesSliceLimit = 100; } // namespace @@ -31,7 +33,7 @@ struct ApiWrap::UserpicsProcess { base::optional slice; bool lastSlice = false; - int loading = -1; + int fileIndex = -1; }; @@ -58,17 +60,41 @@ struct ApiWrap::FileProcess { struct ApiWrap::DialogsProcess { Data::DialogsInfo info; - FnMut done; + FnMut start; + Fn startOne; + Fn sliceOne; + Fn finishOne; + FnMut finish; - int32 offsetDate = 0; + Data::TimeId offsetDate = 0; int32 offsetId = 0; MTPInputPeer offsetPeer = MTP_inputPeerEmpty(); + struct Single; + std::unique_ptr single; + int singleIndex = -1; + +}; + +struct ApiWrap::DialogsProcess::Single { + Single(const Data::DialogInfo &info); + + MTPInputPeer peer; + int32 offsetId = 1; + + base::optional slice; + bool lastSlice = false; + int fileIndex = -1; + }; ApiWrap::FileProcess::FileProcess(const QString &path) : file(path) { } +ApiWrap::DialogsProcess::Single::Single(const Data::DialogInfo &info) +: peer(info.input) { +} + template auto ApiWrap::mainRequest(Request &&request) { return std::move(_mtp.request( @@ -94,16 +120,16 @@ ApiWrap::ApiWrap(Fn)> runner) : _mtp(std::move(runner)) { } -void ApiWrap::setFilesBaseFolder(const QString &folder) { - Expects(folder.endsWith('/')); - - _filesFolder = folder; -} - rpl::producer ApiWrap::errors() const { return _errors.events(); } +void ApiWrap::startExport(const Settings &settings) { + Expects(_settings == nullptr); + + _settings = std::make_unique(settings); +} + void ApiWrap::requestPersonalInfo(FnMut done) { mainRequest(MTPusers_GetFullUser( _user @@ -123,6 +149,8 @@ void ApiWrap::requestUserpics( FnMut start, Fn slice, FnMut finish) { + Expects(_userpicsProcess == nullptr); + _userpicsProcess = std::make_unique(); _userpicsProcess->start = std::move(start); _userpicsProcess->handleSlice = std::move(slice); @@ -153,9 +181,6 @@ void ApiWrap::requestUserpics( void ApiWrap::handleUserpicsSlice(const MTPphotos_Photos &result) { Expects(_userpicsProcess != nullptr); - if (result.type() == mtpc_photos_photos) { - _userpicsProcess->lastSlice = true; - } result.visit([&](const auto &data) { if constexpr (MTPDphotos_photos::Is()) { _userpicsProcess->lastSlice = true; @@ -172,7 +197,7 @@ void ApiWrap::loadUserpicsFiles(Data::UserpicsSlice &&slice) { _userpicsProcess->lastSlice = true; } _userpicsProcess->slice = std::move(slice); - _userpicsProcess->loading = -1; + _userpicsProcess->fileIndex = -1; loadNextUserpic(); } @@ -181,10 +206,10 @@ void ApiWrap::loadNextUserpic() { Expects(_userpicsProcess->slice.has_value()); const auto &list = _userpicsProcess->slice->list; - ++_userpicsProcess->loading; - if (_userpicsProcess->loading < list.size()) { + ++_userpicsProcess->fileIndex; + if (_userpicsProcess->fileIndex < list.size()) { loadFile( - list[_userpicsProcess->loading].image, + list[_userpicsProcess->fileIndex].image, [=](const QString &path) { loadUserpicDone(path); }); return; } @@ -213,11 +238,11 @@ void ApiWrap::loadNextUserpic() { void ApiWrap::loadUserpicDone(const QString &relativePath) { Expects(_userpicsProcess != nullptr); Expects(_userpicsProcess->slice.has_value()); - Expects((_userpicsProcess->loading >= 0) - && (_userpicsProcess->loading + Expects((_userpicsProcess->fileIndex >= 0) + && (_userpicsProcess->fileIndex < _userpicsProcess->slice->list.size())); - const auto index = _userpicsProcess->loading; + const auto index = _userpicsProcess->fileIndex; _userpicsProcess->slice->list[index].image.relativePath = relativePath; loadNextUserpic(); } @@ -250,11 +275,21 @@ void ApiWrap::requestSessions(FnMut done) { }).send(); } -void ApiWrap::requestDialogs(FnMut done) { +void ApiWrap::requestDialogs( + FnMut start, + Fn startOne, + Fn sliceOne, + Fn finishOne, + FnMut finish) { Expects(_dialogsProcess == nullptr); _dialogsProcess = std::make_unique(); - _dialogsProcess->done = std::move(done); + _dialogsProcess->start = std::move(start); + _dialogsProcess->startOne = std::move(startOne); + _dialogsProcess->sliceOne = std::move(sliceOne); + _dialogsProcess->finishOne = std::move(finishOne); + _dialogsProcess->finish = std::move(finish); + requestDialogsSlice(); } @@ -278,33 +313,201 @@ void ApiWrap::requestDialogsSlice() { 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)); + auto info = Data::ParseDialogsInfo(result); + if (finished || info.list.empty()) { + finishDialogsList(); } else { - const auto &last = _dialogsProcess->info.list.back(); + const auto &last = info.list.back(); _dialogsProcess->offsetId = last.topMessageId; _dialogsProcess->offsetDate = last.topMessageDate; _dialogsProcess->offsetPeer = last.input; + + appendDialogsSlice(std::move(info)); + requestDialogsSlice(); } }).send(); } +void ApiWrap::appendDialogsSlice(Data::DialogsInfo &&info) { + Expects(_dialogsProcess != nullptr); + Expects(_settings != nullptr); + + const auto types = _settings->types | _settings->fullChats; + auto filtered = ranges::view::all( + info.list + ) | ranges::view::filter([&](const Data::DialogInfo &info) { + const auto bit = [&] { + using DialogType = Data::DialogInfo::Type; + switch (info.type) { + case DialogType::Personal: + return Settings::Type::PersonalChats; + case DialogType::PrivateGroup: + return Settings::Type::PrivateGroups; + case DialogType::PublicGroup: + return Settings::Type::PublicGroups; + case DialogType::Channel: + return Settings::Type::MyChannels; + } + return Settings::Type(0); + }(); + return (types & bit) != 0; + }); + auto &list = _dialogsProcess->info.list; + list.reserve(list.size()); + for (auto &info : filtered) { + list.push_back(std::move(info)); + } +} + +void ApiWrap::finishDialogsList() { + Expects(_dialogsProcess != nullptr); + + ranges::reverse(_dialogsProcess->info.list); + _dialogsProcess->start(_dialogsProcess->info); + requestNextDialog(); +} + +void ApiWrap::requestNextDialog() { + Expects(_dialogsProcess != nullptr); + Expects(_dialogsProcess->single == nullptr); + + const auto index = ++_dialogsProcess->singleIndex; + if (index < 3) {// _dialogsProcess->info.list.size()) { + const auto &one = _dialogsProcess->info.list[index]; + _dialogsProcess->single = std::make_unique(one); + _dialogsProcess->startOne(one); + requestMessagesSlice(); + return; + } + finishDialogs(); +} + +void ApiWrap::requestMessagesSlice() { + Expects(_dialogsProcess != nullptr); + Expects(_dialogsProcess->single != nullptr); + + const auto process = _dialogsProcess->single.get(); + mainRequest(MTPmessages_GetHistory( + process->peer, + MTP_int(process->offsetId), + MTP_int(0), // offset_date + MTP_int(-kMessagesSliceLimit), + MTP_int(kMessagesSliceLimit), + MTP_int(0), // max_id + MTP_int(0), // min_id + MTP_int(0) // hash + )).done([=](const MTPmessages_Messages &result) mutable { + Expects(_dialogsProcess != nullptr); + Expects(_dialogsProcess->single != nullptr); + + const auto process = _dialogsProcess->single.get(); + result.visit([&](const MTPDmessages_messagesNotModified &data) { + error("Unexpected messagesNotModified received."); + }, [&](const auto &data) { + if constexpr (MTPDmessages_messages::Is()) { + process->lastSlice = true; + } + loadMessagesFiles(Data::ParseMessagesSlice( + data.vmessages, + data.vusers, + data.vchats)); + }); + }).send(); +} + +void ApiWrap::loadMessagesFiles(Data::MessagesSlice &&slice) { + Expects(_dialogsProcess != nullptr); + Expects(_dialogsProcess->single != nullptr); + Expects(!_dialogsProcess->single->slice.has_value()); + + const auto process = _dialogsProcess->single.get(); + if (slice.list.empty()) { + process->lastSlice = true; + } + process->slice = std::move(slice); + process->fileIndex = -1; + + loadNextMessageFile(); +} + +void ApiWrap::loadNextMessageFile() { + Expects(_dialogsProcess != nullptr); + Expects(_dialogsProcess->single != nullptr); + Expects(_dialogsProcess->single->slice.has_value()); + + 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; + } + + _dialogsProcess->sliceOne(*base::take(process->slice)); + + if (process->lastSlice) { + finishMessages(); + return; + } + + Assert(!list.empty()); + process->offsetId = list.back().id + 1; + requestMessagesSlice(); +} + +void ApiWrap::loadMessageFileDone(const QString &relativePath) { + Expects(_dialogsProcess != nullptr); + Expects(_dialogsProcess->single != nullptr); + Expects(_dialogsProcess->single->slice.has_value()); + Expects((_dialogsProcess->single->fileIndex >= 0) + && (_dialogsProcess->single->fileIndex + < _dialogsProcess->single->slice->list.size())); + + const auto process = _dialogsProcess->single.get(); + const auto index = process->fileIndex; + process->slice->list[index].mediaFile.relativePath = relativePath; + loadNextMessageFile(); +} + +void ApiWrap::finishMessages() { + Expects(_dialogsProcess != nullptr); + Expects(_dialogsProcess->single != nullptr); + Expects(!_dialogsProcess->single->slice.has_value()); + + _dialogsProcess->single = nullptr; + _dialogsProcess->finishOne(); + + requestNextDialog(); +} + +void ApiWrap::finishDialogs() { + Expects(_dialogsProcess != nullptr); + Expects(_dialogsProcess->single == nullptr); + + base::take(_dialogsProcess)->finish(); +} + 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; } + using namespace Output; const auto relativePath = File::PrepareRelativePath( - _filesFolder, + _settings->path, file.suggestedPath); _fileProcess = std::make_unique( - _filesFolder + relativePath); + _settings->path + relativePath); _fileProcess->relativePath = relativePath; _fileProcess->location = file.location; _fileProcess->done = std::move(done); @@ -316,8 +519,6 @@ void ApiWrap::loadFile(const Data::File &file, FnMut done) { } else { error(QString("Could not open '%1'.").arg(relativePath)); } - } else if (!file.location.dcId) { - _fileProcess->done(QString()); } else { loadFilePart(); } diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index 1410291596398..d99e57c244a57 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -19,16 +19,20 @@ struct UserpicsSlice; struct ContactsList; struct SessionsList; struct DialogsInfo; +struct DialogInfo; +struct MessagesSlice; } // namespace Data +struct Settings; + class ApiWrap { public: ApiWrap(Fn)> runner); - void setFilesBaseFolder(const QString &folder); - rpl::producer errors() const; + void startExport(const Settings &settings); + void requestPersonalInfo(FnMut done); void requestUserpics( @@ -40,7 +44,12 @@ class ApiWrap { void requestSessions(FnMut done); - void requestDialogs(FnMut done); + void requestDialogs( + FnMut start, + Fn startOne, + Fn sliceOne, + Fn finishOne, + FnMut finish); ~ApiWrap(); @@ -52,6 +61,16 @@ class ApiWrap { void finishUserpics(); void requestDialogsSlice(); + void appendDialogsSlice(Data::DialogsInfo &&info); + void finishDialogsList(); + + void requestNextDialog(); + void requestMessagesSlice(); + void loadMessagesFiles(Data::MessagesSlice &&slice); + void loadNextMessageFile(); + void loadMessageFileDone(const QString &relativePath); + void finishMessages(); + void finishDialogs(); void loadFile(const Data::File &file, FnMut done); void loadFilePart(); @@ -68,7 +87,8 @@ class ApiWrap { void error(const QString &text); MTP::ConcurrentSender _mtp; - QString _filesFolder; + + std::unique_ptr _settings; MTPInputUser _user = MTP_inputUserSelf(); struct UserpicsProcess; diff --git a/Telegram/SourceFiles/export/export_controller.cpp b/Telegram/SourceFiles/export/export_controller.cpp index b6439cb7863cf..1821b6803aa49 100644 --- a/Telegram/SourceFiles/export/export_controller.cpp +++ b/Telegram/SourceFiles/export/export_controller.cpp @@ -157,7 +157,7 @@ void Controller::startExport(const Settings &settings) { return; } _writer = Output::CreateWriter(_settings.format); - _api.setFilesBaseFolder(_settings.path); + _api.startExport(_settings); fillExportSteps(); exportNext(); } @@ -268,8 +268,16 @@ void Controller::exportSessions() { } void Controller::exportDialogs() { - _api.requestDialogs([=](Data::DialogsInfo &&result) { + _api.requestDialogs([=](const Data::DialogsInfo &result) { _writer->writeDialogsStart(result); + }, [=](const Data::DialogInfo &result) { + _writer->writeDialogStart(result); + }, [=](Data::MessagesSlice &&result) { + _writer->writeMessagesSlice(result); + }, [=] { + _writer->writeDialogEnd(); + }, [=] { + _writer->writeDialogsEnd(); exportNext(); }); } diff --git a/Telegram/SourceFiles/export/export_settings.h b/Telegram/SourceFiles/export/export_settings.h index 58bed480d714a..0913821b43857 100644 --- a/Telegram/SourceFiles/export/export_settings.h +++ b/Telegram/SourceFiles/export/export_settings.h @@ -53,6 +53,7 @@ struct Settings { Output::Format format = Output::Format(); Types types = DefaultTypes(); + Types fullChats = DefaultFullChats(); MediaSettings defaultMedia; base::flat_map customMedia; @@ -64,6 +65,10 @@ struct Settings { | Type::PersonalChats; } + static inline Types DefaultFullChats() { + return Type::PersonalChats; + } + }; } // namespace Export diff --git a/Telegram/SourceFiles/export/output/export_output_file.cpp b/Telegram/SourceFiles/export/output/export_output_file.cpp index ebf8ae1622e3a..bd68b611f9a26 100644 --- a/Telegram/SourceFiles/export/output/export_output_file.cpp +++ b/Telegram/SourceFiles/export/output/export_output_file.cpp @@ -17,6 +17,10 @@ namespace Output { File::File(const QString &path) : _path(path) { } +bool File::empty() const { + return !_offset; +} + File::Result File::writeBlock(const QByteArray &block) { const auto result = writeBlockAttempt(block); if (result != Result::Success) { diff --git a/Telegram/SourceFiles/export/output/export_output_file.h b/Telegram/SourceFiles/export/output/export_output_file.h index f8083a1e52ac2..680b25e2d29cc 100644 --- a/Telegram/SourceFiles/export/output/export_output_file.h +++ b/Telegram/SourceFiles/export/output/export_output_file.h @@ -20,6 +20,8 @@ class File { public: File(const QString &path); + bool empty() const; + enum class Result { Success, Error, diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp index 93f3bdf31ed40..0a817facb85a0 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.cpp +++ b/Telegram/SourceFiles/export/output/export_output_text.cpp @@ -29,7 +29,7 @@ void SerializeMultiline( auto offset = 0; do { appendTo.append("> "); - appendTo.append(data + offset, newline).append(kLineBreak); + appendTo.append(data + offset, newline - offset).append(kLineBreak); offset = newline + 1; newline = value.indexOf('\n', offset); } while (newline > 0); @@ -89,7 +89,7 @@ bool TextWriter::start(const QString &folder) { Expects(folder.endsWith('/')); _folder = folder; - _result = std::make_unique(_folder + "result.txt"); + _result = fileWithRelativePath(mainFileRelativePath()); return true; } @@ -128,7 +128,7 @@ bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) { auto lines = QByteArray(); for (const auto &userpic : data.list) { if (!userpic.date) { - lines.append("(empty photo)"); + lines.append("(deleted photo)"); } else { lines.append(Data::FormatDateTime(userpic.date)).append(" - "); if (userpic.image.relativePath.isEmpty()) { @@ -153,17 +153,17 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) { return true; } - const auto file = std::make_unique(_folder + "contacts.txt"); + const auto file = fileWithRelativePath("contacts.txt"); auto list = std::vector(); list.reserve(data.list.size()); for (const auto &index : Data::SortedContactsIndices(data)) { const auto &contact = data.list[index]; if (!contact.id) { - list.push_back("(user unavailable)"); + list.push_back("(user unavailable)" + kLineBreak); } else if (contact.firstName.isEmpty() && contact.lastName.isEmpty() && contact.phoneNumber.isEmpty()) { - list.push_back("(empty user)" + kLineBreak); + list.push_back("(deleted user)" + kLineBreak); } else { list.push_back(SerializeKeyValue({ { "First name", contact.firstName }, @@ -192,7 +192,7 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) { return true; } - const auto file = std::make_unique(_folder + "sessions.txt"); + const auto file = fileWithRelativePath("sessions.txt"); auto list = std::vector(); list.reserve(data.list.size()); for (const auto &session : data.list) { @@ -231,6 +231,8 @@ bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) { return true; } + _dialogsCount = data.list.size(); + using Type = Data::DialogInfo::Type; const auto TypeString = [](Type type) { switch (type) { @@ -242,20 +244,31 @@ bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) { } Unexpected("Dialog type in TypeString."); }; + const auto NameString = []( + const Data::Utf8String &name, + Type type) -> QByteArray { + if (!name.isEmpty()) { + return name; + } + switch (type) { + case Type::Unknown: return "(unknown)"; + case Type::Personal: return "(deleted user)"; + case Type::PrivateGroup: + case Type::PublicGroup: return "(deleted group)"; + case Type::Channel: return "(deleted 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"); + const auto file = fileWithRelativePath("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"; + const auto number = Data::NumberToString(++index, digits, '0'); + const auto path = "Chats/chat_" + number + ".txt"; list.push_back(SerializeKeyValue({ - { "Name", dialog.name }, + { "Name", NameString(dialog.name, dialog.type) }, { "Type", TypeString(dialog.type) }, { "Content", path } })); @@ -273,14 +286,38 @@ bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) { } bool TextWriter::writeDialogStart(const Data::DialogInfo &data) { + Expects(_dialog == nullptr); + Expects(_dialogIndex < _dialogsCount); + + const auto digits = Data::NumberToString(_dialogsCount - 1).size(); + const auto number = Data::NumberToString(++_dialogIndex, digits, '0'); + _dialog = fileWithRelativePath("Chats/chat_" + number + ".txt"); return true; } bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) { - return true; + Expects(_dialog != nullptr); + + auto list = std::vector(); + list.reserve(data.list.size()); + auto index = 0; + for (const auto &message : data.list) { + list.push_back(SerializeKeyValue({ + { "ID", Data::NumberToString(message.id) }, + { "Date", Data::FormatDateTime(message.date) }, + { "Text", message.text } + })); + } + const auto full = _dialog->empty() + ? JoinList(kLineBreak, list) + : kLineBreak + JoinList(kLineBreak, list); + return _dialog->writeBlock(full) == File::Result::Success; } bool TextWriter::writeDialogEnd() { + Expects(_dialog != nullptr); + + _dialog = nullptr; return true; } @@ -293,7 +330,20 @@ bool TextWriter::finish() { } QString TextWriter::mainFilePath() { - return _folder + "result.txt"; + return pathWithRelativePath(mainFileRelativePath()); +} + +QString TextWriter::mainFileRelativePath() const { + return "result.txt"; +} + +QString TextWriter::pathWithRelativePath(const QString &path) const { + return _folder + path; +} + +std::unique_ptr TextWriter::fileWithRelativePath( + const QString &path) const { + return std::make_unique(_folder + path); } } // namespace Output diff --git a/Telegram/SourceFiles/export/output/export_output_text.h b/Telegram/SourceFiles/export/output/export_output_text.h index cc857c4c1a7c3..16170d559ae1c 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.h +++ b/Telegram/SourceFiles/export/output/export_output_text.h @@ -38,11 +38,19 @@ class TextWriter : public AbstractWriter { QString mainFilePath() override; private: + QString mainFileRelativePath() const; + QString pathWithRelativePath(const QString &path) const; + std::unique_ptr fileWithRelativePath(const QString &path) const; + QString _folder; std::unique_ptr _result; int _userpicsCount = 0; + int _dialogsCount = 0; + int _dialogIndex = 0; + std::unique_ptr _dialog; + }; } // namespace Output