From 0a1a5ed70eab299b1d7e9485e1e768672bc2d5c4 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 9 Jun 2018 14:51:22 +0300 Subject: [PATCH] Use abstract export writer for different formats. --- .../export/data/export_data_types.cpp | 80 ++++++ .../export/data/export_data_types.h | 113 +++++++++ .../SourceFiles/export/export_controller.cpp | 229 ++++++++++++++++-- .../SourceFiles/export/export_controller.h | 2 +- Telegram/SourceFiles/export/export_pch.h | 4 + Telegram/SourceFiles/export/export_settings.h | 32 +-- .../export/output/export_output_abstract.cpp | 20 ++ .../export/output/export_output_abstract.h | 63 +++++ .../export/output/export_output_file.cpp | 55 +++++ .../export/output/export_output_file.h | 41 ++++ .../export/output/export_output_text.cpp | 154 ++++++++++++ .../export/output/export_output_text.h | 49 ++++ .../view/export_view_panel_controller.cpp | 2 +- .../export/view/export_view_settings.cpp | 2 +- .../SourceFiles/mtproto/concurrent_sender.h | 111 +++++++++ .../SourceFiles/ui/widgets/separate_panel.cpp | 6 +- Telegram/gyp/lib_export.gyp | 8 + 17 files changed, 927 insertions(+), 44 deletions(-) create mode 100644 Telegram/SourceFiles/export/data/export_data_types.cpp create mode 100644 Telegram/SourceFiles/export/data/export_data_types.h create mode 100644 Telegram/SourceFiles/export/output/export_output_abstract.cpp create mode 100644 Telegram/SourceFiles/export/output/export_output_abstract.h create mode 100644 Telegram/SourceFiles/export/output/export_output_file.cpp create mode 100644 Telegram/SourceFiles/export/output/export_output_file.h create mode 100644 Telegram/SourceFiles/export/output/export_output_text.cpp create mode 100644 Telegram/SourceFiles/export/output/export_output_text.h diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp new file mode 100644 index 00000000000000..2be84abcfbb362 --- /dev/null +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -0,0 +1,80 @@ +/* +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 +*/ +#include "export/data/export_data_types.h" + +namespace App { // Hackish.. +QString formatPhone(QString phone); +} // namespace App + +namespace Export { +namespace Data { + +Utf8String ParseString(const MTPstring &data) { + return data.v; +} + +PersonalInfo ParsePersonalInfo(const MTPUserFull &data) { + Expects(data.type() == mtpc_userFull); + + const auto &fields = data.c_userFull(); + const auto &small = fields.vuser.c_user(); + auto result = PersonalInfo(); + if (small.has_first_name()) { + result.firstName = ParseString(small.vfirst_name); + } + if (small.has_last_name()) { + result.lastName = ParseString(small.vlast_name); + } + if (small.has_phone()) { + result.phoneNumber = ParseString(small.vphone); + } + if (small.has_username()) { + result.username = ParseString(small.vusername); + } + if (fields.has_about()) { + result.bio = ParseString(fields.vabout); + } + return result; +} + +UserpicsSlice ParseUserpicsSlice(const MTPVector &data) { + const auto &list = data.v; + auto result = UserpicsSlice(); + result.list.reserve(list.size()); + for (const auto &photo : list) { + switch (photo.type()) { + case mtpc_photo: { + const auto &fields = photo.c_photo(); + auto userpic = Userpic(); + userpic.id = fields.vid.v; + userpic.date = QDateTime::fromTime_t(fields.vdate.v); + userpic.image = File{ "(not saved)" }; + result.list.push_back(std::move(userpic)); + } break; + + case mtpc_photoEmpty: { + const auto &fields = photo.c_photoEmpty(); + auto userpic = Userpic(); + userpic.id = fields.vid.v; + result.list.push_back(std::move(userpic)); + } break; + + default: Unexpected("Photo type in ParseUserpicsSlice."); + } + } + return result; +} + +Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) { + return phoneNumber.isEmpty() + ? Utf8String() + : App::formatPhone(QString::fromUtf8(phoneNumber)).toUtf8(); +} + +} // namespace Data +} // namespace Export diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h new file mode 100644 index 00000000000000..4344a900f37b1c --- /dev/null +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -0,0 +1,113 @@ +/* +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 "scheme.h" + +#include +#include +#include +#include + +namespace Export { +namespace Data { + +using Utf8String = QByteArray; + +Utf8String ParseString(const MTPstring &data); + +template +inline auto NumberToString(Type value) +-> std::enable_if_t, Utf8String> { + const auto result = std::to_string(value); + return QByteArray(result.data(), int(result.size())); +} + +struct PersonalInfo { + Utf8String firstName; + Utf8String lastName; + Utf8String phoneNumber; + Utf8String username; + Utf8String bio; +}; + +PersonalInfo ParsePersonalInfo(const MTPUserFull &data); + +struct UserpicsInfo { + int count = 0; +}; + +struct File { + QString relativePath; +}; + +struct Userpic { + uint64 id = 0; + QDateTime date; + File image; +}; + +struct UserpicsSlice { + std::vector list; +}; + +UserpicsSlice ParseUserpicsSlice(const MTPVector &data); + +struct Contact { + Utf8String firstName; + Utf8String lastName; + Utf8String phoneNumber; +}; + +struct ContactsList { + std::vector list; +}; + +struct Session { + Utf8String platform; + Utf8String deviceModel; + Utf8String systemVersion; + Utf8String applicationName; + Utf8String applicationVersion; + QDateTime created; + QDateTime lastActive; + Utf8String ip; + Utf8String country; + Utf8String region; +}; + +struct SessionsList { + std::vector list; +}; + +struct ChatsInfo { + int count = 0; +}; + +struct ChatInfo { + enum class Type { + Personal, + Group, + Channel, + }; + Type type = Type::Personal; + QString name; +}; + +struct Message { + int id = 0; +}; + +struct MessagesSlice { + std::vector list; +}; + +Utf8String FormatPhoneNumber(const Utf8String &phoneNumber); + +} // namespace Data +} // namespace Export diff --git a/Telegram/SourceFiles/export/export_controller.cpp b/Telegram/SourceFiles/export/export_controller.cpp index d1898284af340b..d1bb7ce10e3d01 100644 --- a/Telegram/SourceFiles/export/export_controller.cpp +++ b/Telegram/SourceFiles/export/export_controller.cpp @@ -8,10 +8,17 @@ For license and copyright information please follow this link: #include "export/export_controller.h" #include "export/export_settings.h" +#include "export/data/export_data_types.h" +#include "export/output/export_output_abstract.h" #include "mtproto/rpc_sender.h" #include "mtproto/concurrent_sender.h" namespace Export { +namespace { + +constexpr auto kUserpicsSliceLimit = 100; + +} // namespace class Controller { public: @@ -30,6 +37,8 @@ class Controller { void startExport(const Settings &settings); private: + using Step = ProcessingState::Step; + void setState(State &&state); void apiError(const RPCError &error); void apiError(const QString &error); @@ -39,6 +48,18 @@ class Controller { void requestPasswordState(); void passwordStateDone(const MTPaccount_Password &password); + void fillExportSteps(); + void exportNext(); + void exportPersonalInfo(); + void exportUserpics(); + void exportContacts(); + void exportSessions(); + void exportChats(); + + void exportUserpicsSlice(const MTPphotos_Photos &result); + + bool normalizePath(); + MTP::ConcurrentSender _mtp; Settings _settings; @@ -48,6 +69,12 @@ class Controller { mtpRequestId _passwordRequestId = 0; + std::unique_ptr _writer; + std::vector _steps; + int _stepIndex = -1; + + MTPInputUser _user = MTP_inputUserSelf(); + }; Controller::Controller(crl::weak_on_queue weak) @@ -130,47 +157,197 @@ void Controller::cancelUnconfirmedPassword() { void Controller::startExport(const Settings &settings) { _settings = base::duplicate(settings); - setState(ProcessingState()); + if (!normalizePath()) { + ioError(_settings.path); + return; + } + _writer = Output::CreateWriter(_settings.format); + fillExportSteps(); + exportNext(); +} + +bool Controller::normalizePath() { + const auto check = [&] { + return QDir().mkpath(_settings.path); + }; + QDir folder(_settings.path); + const auto path = folder.absolutePath(); + _settings.path = path + '/'; + if (!folder.exists()) { + return check(); + } + const auto list = folder.entryInfoList(); + if (list.isEmpty()) { + return true; + } + const auto date = QDate::currentDate(); + const auto base = QString("DataExport.%1.%2.%3" + ).arg(date.day(), 2, 10, QChar('0') + ).arg(date.month(), 2, 10, QChar('0') + ).arg(date.year()); + const auto add = [&](int i) { + return base + (i ? " (" + QString::number(i) + ')' : QString()); + }; + auto index = 0; + while (QDir(_settings.path + add(index)).exists()) { + ++index; + } + _settings.path += add(index) + '/'; + return check(); +} + +void Controller::fillExportSteps() { + using Type = Settings::Type; + if (_settings.types & Type::PersonalInfo) { + _steps.push_back(Step::PersonalInfo); + } + if (_settings.types & Type::Userpics) { + _steps.push_back(Step::Userpics); + } + if (_settings.types & Type::Contacts) { + _steps.push_back(Step::Contacts); + } + if (_settings.types & Type::Sessions) { + _steps.push_back(Step::Sessions); + } + const auto chatTypes = Type::PersonalChats + | Type::PrivateGroups + | Type::PublicGroups + | Type::MyChannels; + if (_settings.types & chatTypes) { + _steps.push_back(Step::Chats); + } +} + +void Controller::exportNext() { + if (!++_stepIndex) { + _writer->start(_settings.path); + } + if (_stepIndex >= _steps.size()) { + _writer->finish(); + setFinishedState(); + return; + } + const auto step = _steps[_stepIndex]; + switch (step) { + case Step::PersonalInfo: return exportPersonalInfo(); + case Step::Userpics: return exportUserpics(); + case Step::Contacts: return exportContacts(); + case Step::Sessions: return exportSessions(); + case Step::Chats: return exportChats(); + } + Unexpected("Step in Controller::exportNext."); +} + +void Controller::exportPersonalInfo() { + if (!(_settings.types & Settings::Type::PersonalInfo)) { + exportUserpics(); + return; + } _mtp.request(MTPusers_GetFullUser( - MTP_inputUserSelf() + _user )).done([=](const MTPUserFull &result) { Expects(result.type() == mtpc_userFull); const auto &full = result.c_userFull(); - if (full.vuser.type() != mtpc_user) { + if (full.vuser.type() == mtpc_user) { + _writer->writePersonal(Data::ParsePersonalInfo(result)); + exportNext(); + } else { apiError("Bad user type."); - return; } - const auto &user = full.vuser.c_user(); + }).fail([=](const RPCError &error) { + apiError(error); + }).send(); +} - QFile f(_settings.path + "personal.txt"); - if (!f.open(QIODevice::WriteOnly)) { - ioError(f.fileName()); - return; - } - QTextStream stream(&f); - stream.setCodec("UTF-8"); - if (user.has_first_name()) { - stream << "First name: " << qs(user.vfirst_name) << "\n"; - } - if (user.has_last_name()) { - stream << "Last name: " << qs(user.vlast_name) << "\n"; - } - if (user.has_phone()) { - stream << "Phone number: " << qs(user.vphone) << "\n"; - } - if (user.has_username()) { - stream << "Username: @" << qs(user.vusername) << "\n"; - } - setFinishedState(); +void Controller::exportUserpics() { + _mtp.request(MTPphotos_GetUserPhotos( + _user, + MTP_int(0), + MTP_long(0), + MTP_int(kUserpicsSliceLimit) + )).done([=](const MTPphotos_Photos &result) { + _writer->writeUserpicsStart([&] { + auto info = Data::UserpicsInfo(); + switch (result.type()) { + case mtpc_photos_photos: { + const auto &data = result.c_photos_photos(); + info.count = data.vphotos.v.size(); + } break; + + case mtpc_photos_photosSlice: { + const auto &data = result.c_photos_photosSlice(); + info.count = data.vcount.v; + } break; + + default: Unexpected("Photos type in Controller::exportUserpics."); + } + return info; + }()); + + exportUserpicsSlice(result); }).fail([=](const RPCError &error) { apiError(error); }).send(); } +void Controller::exportUserpicsSlice(const MTPphotos_Photos &result) { + const auto finish = [&] { + _writer->writeUserpicsEnd(); + exportNext(); + }; + switch (result.type()) { + case mtpc_photos_photos: { + const auto &data = result.c_photos_photos(); + + _writer->writeUserpicsSlice( + Data::ParseUserpicsSlice(data.vphotos)); + + finish(); + } break; + + case mtpc_photos_photosSlice: { + const auto &data = result.c_photos_photosSlice(); + + const auto slice = Data::ParseUserpicsSlice(data.vphotos); + _writer->writeUserpicsSlice(slice); + + if (slice.list.empty()) { + finish(); + } else { + _mtp.request(MTPphotos_GetUserPhotos( + _user, + MTP_int(0), + MTP_long(slice.list.back().id), + MTP_int(kUserpicsSliceLimit) + )).done([=](const MTPphotos_Photos &result) { + exportUserpicsSlice(result); + }).fail([=](const RPCError &error) { + apiError(error); + }).send(); + } + } break; + + default: Unexpected("Photos type in Controller::exportUserpicsSlice."); + } +} + +void Controller::exportContacts() { + exportNext(); +} + +void Controller::exportSessions() { + exportNext(); +} + +void Controller::exportChats() { + exportNext(); +} + void Controller::setFinishedState() { - setState(FinishedState{ _settings.path }); + setState(FinishedState{ _writer->mainFilePath() }); } ControllerWrap::ControllerWrap() { diff --git a/Telegram/SourceFiles/export/export_controller.h b/Telegram/SourceFiles/export/export_controller.h index 7e10a89129ac1a..f9281d30301b45 100644 --- a/Telegram/SourceFiles/export/export_controller.h +++ b/Telegram/SourceFiles/export/export_controller.h @@ -28,7 +28,7 @@ struct PasswordCheckState { struct ProcessingState { enum class Step { PersonalInfo, - Avatars, + Userpics, Contacts, Sessions, Chats, diff --git a/Telegram/SourceFiles/export/export_pch.h b/Telegram/SourceFiles/export/export_pch.h index 62a6a0222e007e..fa05b04d3812b3 100644 --- a/Telegram/SourceFiles/export/export_pch.h +++ b/Telegram/SourceFiles/export/export_pch.h @@ -9,7 +9,11 @@ For license and copyright information please follow this link: #include #include #include +#include #include #include +#include +#include + #include "scheme.h" #include "logs.h" diff --git a/Telegram/SourceFiles/export/export_settings.h b/Telegram/SourceFiles/export/export_settings.h index 33ebbfb0e90bd2..58bed480d714a0 100644 --- a/Telegram/SourceFiles/export/export_settings.h +++ b/Telegram/SourceFiles/export/export_settings.h @@ -11,14 +11,17 @@ For license and copyright information please follow this link: #include "base/flat_map.h" namespace Export { +namespace Output { +enum class Format; +} // namespace Output struct MediaSettings { enum class Type { - Photo, - Video, - Sticker, - GIF, - File, + Photo = 0x01, + Video = 0x02, + Sticker = 0x04, + GIF = 0x08, + File = 0x10, }; using Types = base::flags; friend inline constexpr auto is_flag_type(Type) { return true; }; @@ -34,19 +37,20 @@ struct MediaSettings { struct Settings { enum class Type { - PersonalInfo, - Avatars, - Contacts, - Sessions, - PersonalChats, - PrivateGroups, - PublicGroups, - MyChannels, + PersonalInfo = 0x01, + Userpics = 0x02, + Contacts = 0x04, + Sessions = 0x08, + PersonalChats = 0x10, + PrivateGroups = 0x20, + PublicGroups = 0x40, + MyChannels = 0x80, }; using Types = base::flags; friend inline constexpr auto is_flag_type(Type) { return true; }; QString path; + Output::Format format = Output::Format(); Types types = DefaultTypes(); MediaSettings defaultMedia; @@ -54,7 +58,7 @@ struct Settings { static inline Types DefaultTypes() { return Type::PersonalInfo - | Type::Avatars + | Type::Userpics | Type::Contacts | Type::Sessions | Type::PersonalChats; diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.cpp b/Telegram/SourceFiles/export/output/export_output_abstract.cpp new file mode 100644 index 00000000000000..eddd9ff08e33c5 --- /dev/null +++ b/Telegram/SourceFiles/export/output/export_output_abstract.cpp @@ -0,0 +1,20 @@ +/* +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 +*/ +#include "export/output/export_output_abstract.h" + +#include "export/output/export_output_text.h" + +namespace Export { +namespace Output { + +std::unique_ptr CreateWriter(Format format) { + return std::make_unique(); +} + +} // namespace Output +} // namespace Export diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.h b/Telegram/SourceFiles/export/output/export_output_abstract.h new file mode 100644 index 00000000000000..c16152d5391aa6 --- /dev/null +++ b/Telegram/SourceFiles/export/output/export_output_abstract.h @@ -0,0 +1,63 @@ +/* +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 Export { +namespace Data { +struct PersonalInfo; +struct UserpicsInfo; +struct UserpicsSlice; +struct ContactsList; +struct SessionsList; +struct ChatsInfo; +struct ChatInfo; +struct MessagesSlice; +} // namespace Data + +namespace Output { + +enum class Format { + Text, + Html, + Json, +}; + +class AbstractWriter { +public: + virtual bool start(const QString &folder) = 0; + + virtual bool writePersonal(const Data::PersonalInfo &data) = 0; + + virtual bool writeUserpicsStart(const Data::UserpicsInfo &data) = 0; + virtual bool writeUserpicsSlice(const Data::UserpicsSlice &data) = 0; + virtual bool writeUserpicsEnd() = 0; + + virtual bool writeContactsList(const Data::ContactsList &data) = 0; + + 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 writeMessagesSlice(const Data::MessagesSlice &data) = 0; + virtual bool writeChatEnd() = 0; + virtual bool writeChatsEnd() = 0; + + virtual bool finish() = 0; + + virtual QString mainFilePath() = 0; + + virtual ~AbstractWriter() = default; + +}; + +std::unique_ptr CreateWriter(Format format); + +} // namespace Output +} // namespace Export diff --git a/Telegram/SourceFiles/export/output/export_output_file.cpp b/Telegram/SourceFiles/export/output/export_output_file.cpp new file mode 100644 index 00000000000000..07b8f82a18e4f5 --- /dev/null +++ b/Telegram/SourceFiles/export/output/export_output_file.cpp @@ -0,0 +1,55 @@ +/* +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 +*/ +#include "export/output/export_output_file.h" + +#include + +namespace Export { +namespace Output { + +File::File(const QString &path) : _path(path) { +} + +File::Result File::writeBlock(const QByteArray &block) { + const auto result = writeBlockAttempt(block); + if (result != Result::Success) { + _file.clear(); + } + return result; +} + +File::Result File::writeBlockAttempt(const QByteArray &block) { + if (const auto result = reopen(); result != Result::Success) { + return result; + } + return (_file->write(block) == block.size() && _file->flush()) + ? Result::Success + : Result::Error; +} + +File::Result File::reopen() { + if (_file && _file->isOpen()) { + return Result::Success; + } + _file.emplace(_path); + if (_file->exists()) { + if (_file->size() < _offset) { + return Result::FatalError; + } else if (!_file->resize(_offset)) { + return Result::Error; + } + } else if (_offset > 0) { + return Result::FatalError; + } + return _file->open(QIODevice::Append) + ? Result::Success + : Result::Error; +} + +} // namespace Output +} // namespace File diff --git a/Telegram/SourceFiles/export/output/export_output_file.h b/Telegram/SourceFiles/export/output/export_output_file.h new file mode 100644 index 00000000000000..8d4dad7e24138f --- /dev/null +++ b/Telegram/SourceFiles/export/output/export_output_file.h @@ -0,0 +1,41 @@ +/* +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 "base/optional.h" + +#include +#include +#include + +namespace Export { +namespace Output { + +class File { +public: + File(const QString &path); + + enum class Result { + Success, + Error, + FatalError, + }; + Result writeBlock(const QByteArray &block); + +private: + Result reopen(); + Result writeBlockAttempt(const QByteArray &block); + + QString _path; + int _offset = 0; + base::optional _file; + +}; + +} // namespace Output +} // namespace File diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp new file mode 100644 index 00000000000000..192abd0110445a --- /dev/null +++ b/Telegram/SourceFiles/export/output/export_output_text.cpp @@ -0,0 +1,154 @@ +/* +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 +*/ +#include "export/output/export_output_text.h" + +#include "export/data/export_data_types.h" + +#include + +namespace Export { +namespace Output { +namespace { + +#ifdef Q_OS_WIN +const auto kLineBreak = QByteArrayLiteral("\r\n"); +#else // Q_OS_WIN +const auto kLineBreak = QByteArrayLiteral("\n"); +#endif // Q_OS_WIN + +void SerializeMultiline( + QByteArray &appendTo, + const QByteArray &value, + int newline) { + const auto data = value.data(); + auto offset = 0; + do { + appendTo.append("> "); + appendTo.append(data + offset, newline).append(kLineBreak); + offset = newline + 1; + newline = value.indexOf('\n', offset); + } while (newline > 0); +} + +QByteArray SerializeKeyValue( + std::vector> &&values) { + auto result = QByteArray(); + for (const auto &[key, value] : values) { + if (value.isEmpty()) { + continue; + } + result.append(key); + if (const auto newline = value.indexOf('\n'); newline >= 0) { + result.append(':').append(kLineBreak); + SerializeMultiline(result, value, newline); + } else { + result.append(": ").append(value).append(kLineBreak); + } + } + return result; +} + +Data::Utf8String FormatUsername(const Data::Utf8String &username) { + return username.isEmpty() ? username : ('@' + username); +} + +} // namespace + +bool TextWriter::start(const QString &folder) { + Expects(folder.endsWith('/')); + + _folder = folder; + _result = std::make_unique(_folder + "result.txt"); + return true; +} + +bool TextWriter::writePersonal(const Data::PersonalInfo &data) { + Expects(_result != nullptr); + + const auto serialized = "Personal information" + + kLineBreak + + kLineBreak + + SerializeKeyValue({ + { "First name", data.firstName }, + { "Last name", data.lastName }, + { "Phone number", Data::FormatPhoneNumber(data.phoneNumber) }, + { "Username", FormatUsername(data.username) }, + { "Bio", data.bio }, + }) + + kLineBreak; + return _result->writeBlock(serialized) == File::Result::Success; +} + +bool TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) { + Expects(_result != nullptr); + + _userpicsCount = data.count; + if (!_userpicsCount) { + return true; + } + const auto serialized = "Personal photos " + "(" + Data::NumberToString(_userpicsCount) + ")" + + kLineBreak + + kLineBreak; + return _result->writeBlock(serialized) == File::Result::Success; +} + +bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) { + auto lines = QByteArray(); + for (const auto &userpic : data.list) { + lines.append(userpic.date.toString().toUtf8()).append(": "); + lines.append(userpic.image.relativePath.toUtf8()); + lines.append(kLineBreak); + } + return _result->writeBlock(lines) == File::Result::Success; +} + +bool TextWriter::writeUserpicsEnd() { + return (_userpicsCount > 0) + ? _result->writeBlock(kLineBreak) == File::Result::Success + : true; +} + +bool TextWriter::writeContactsList(const Data::ContactsList &data) { + return true; +} + +bool TextWriter::writeSessionsList(const Data::SessionsList &data) { + return true; +} + +bool TextWriter::writeChatsStart(const Data::ChatsInfo &data) { + return true; +} + +bool TextWriter::writeChatStart(const Data::ChatInfo &data) { + return true; +} + +bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) { + return true; +} + +bool TextWriter::writeChatEnd() { + return true; +} + +bool TextWriter::writeChatsEnd() { + return true; +} + +bool TextWriter::finish() { + return true; +} + +QString TextWriter::mainFilePath() { + return _folder + "result.txt"; +} + +} // namespace Output +} // namespace Export diff --git a/Telegram/SourceFiles/export/output/export_output_text.h b/Telegram/SourceFiles/export/output/export_output_text.h new file mode 100644 index 00000000000000..8a1083da957d81 --- /dev/null +++ b/Telegram/SourceFiles/export/output/export_output_text.h @@ -0,0 +1,49 @@ +/* +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 "export/output/export_output_abstract.h" +#include "export/output/export_output_file.h" + +namespace Export { +namespace Output { + +class TextWriter : public AbstractWriter { +public: + bool start(const QString &folder) override; + + bool writePersonal(const Data::PersonalInfo &data) override; + + bool writeUserpicsStart(const Data::UserpicsInfo &data) override; + bool writeUserpicsSlice(const Data::UserpicsSlice &data) override; + bool writeUserpicsEnd() override; + + bool writeContactsList(const Data::ContactsList &data) override; + + bool writeSessionsList(const Data::SessionsList &data) override; + + bool writeChatsStart(const Data::ChatsInfo &data) override; + bool writeChatStart(const Data::ChatInfo &data) override; + bool writeMessagesSlice(const Data::MessagesSlice &data) override; + bool writeChatEnd() override; + bool writeChatsEnd() override; + + bool finish() override; + + QString mainFilePath() override; + +private: + QString _folder; + + std::unique_ptr _result; + int _userpicsCount = 0; + +}; + +} // namespace Output +} // namespace Export diff --git a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp index 5996d510378da8..bcdcd4a4abfd1a 100644 --- a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp +++ b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp @@ -74,7 +74,7 @@ void PanelController::updateState(State &&state) { done->showClicks( ) | rpl::start_with_next([=] { - File::ShowInFolder(path + "personal.txt"); + File::ShowInFolder(path); _panel->hideGetDuration(); }, done->lifetime()); diff --git a/Telegram/SourceFiles/export/view/export_view_settings.cpp b/Telegram/SourceFiles/export/view/export_view_settings.cpp index 986283d89ac27c..5089a512954358 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.cpp +++ b/Telegram/SourceFiles/export/view/export_view_settings.cpp @@ -65,7 +65,7 @@ void SettingsWidget::setupContent() { refreshButtonsCallback(); }, lifetime()); }; - addOption(lng_export_option_info, Type::PersonalInfo | Type::Avatars); + addOption(lng_export_option_info, Type::PersonalInfo | Type::Userpics); addOption(lng_export_option_contacts, Type::Contacts); addOption(lng_export_option_sessions, Type::Sessions); refreshButtonsCallback(); diff --git a/Telegram/SourceFiles/mtproto/concurrent_sender.h b/Telegram/SourceFiles/mtproto/concurrent_sender.h index b7af429766636f..d975ccc3025212 100644 --- a/Telegram/SourceFiles/mtproto/concurrent_sender.h +++ b/Telegram/SourceFiles/mtproto/concurrent_sender.h @@ -89,6 +89,8 @@ class ConcurrentSender : public base::has_weak_ptr { template class SpecificRequestBuilder : public RequestBuilder { public: + using Response = typename Request::ResponseType; + SpecificRequestBuilder( const SpecificRequestBuilder &other) = delete; SpecificRequestBuilder( @@ -102,10 +104,32 @@ class ConcurrentSender : public base::has_weak_ptr { ShiftedDcId dcId) noexcept; [[nodiscard]] SpecificRequestBuilder &afterDelay( TimeMs ms) noexcept; + +#ifdef _DEBUG + // Allow code completion to show response type. + [[nodiscard]] SpecificRequestBuilder &done(Fn &&handler); + [[nodiscard]] SpecificRequestBuilder &done(Fn &&handler); + [[nodiscard]] SpecificRequestBuilder &done(Fn &&handler); + [[nodiscard]] SpecificRequestBuilder &done(Fn &&handler); + [[nodiscard]] SpecificRequestBuilder &fail(Fn &&handler); + [[nodiscard]] SpecificRequestBuilder &fail(Fn &&handler); + [[nodiscard]] SpecificRequestBuilder &fail(Fn &&handler); + [[nodiscard]] SpecificRequestBuilder &fail(Fn &&handler); +#else // _DEBUG template [[nodiscard]] SpecificRequestBuilder &done(Handler &&handler); template [[nodiscard]] SpecificRequestBuilder &fail(Handler &&handler); +#endif // _DEBUG + [[nodiscard]] SpecificRequestBuilder &handleFloodErrors() noexcept; [[nodiscard]] SpecificRequestBuilder &handleAllErrors() noexcept; [[nodiscard]] SpecificRequestBuilder &afterRequest( @@ -228,6 +252,91 @@ template return *this; } +#ifdef _DEBUG +// Allow code completion to show response type. +template +[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::done( + Fn &&handler) +-> SpecificRequestBuilder & { + setDoneHandler([handler = std::move(handler)]( + mtpRequestId requestId, + Response &&result) mutable { + std::move(handler)(std::move(result)); + }); + return *this; +} + +template +[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::done( + Fn &&handler) +-> SpecificRequestBuilder & { + setDoneHandler(std::move(handler)); + return *this; +} + +template +[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::done( + Fn &&handler) -> SpecificRequestBuilder & { + setDoneHandler([handler = std::move(handler)]( + mtpRequestId requestId, + Response &&result) mutable { + std::move(handler)(requestId); + }); + return *this; +} + +template +[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::done( + Fn &&handler) -> SpecificRequestBuilder & { + setDoneHandler([handler = std::move(handler)]( + mtpRequestId requestId, + Response &&result) mutable { + std::move(handler)(); + }); + return *this; +} + +template +[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::fail( + Fn &&handler) -> SpecificRequestBuilder & { + setFailHandler([handler = std::move(handler)]( + mtpRequestId requestId, + RPCError &&error) mutable { + std::move(handler)(std::move(error)); + }); + return *this; +} + +template +[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::fail( + Fn &&handler) +-> SpecificRequestBuilder & { + setFailHandler(std::move(handler)); + return *this; +} + +template +[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::fail( + Fn &&handler) -> SpecificRequestBuilder & { + setFailHandler([handler = std::move(handler)]( + mtpRequestId requestId, + RPCError &&error) mutable { + std::move(handler)(requestId); + }); + return *this; +} + +template +[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::fail( + Fn &&handler) -> SpecificRequestBuilder & { + setFailHandler([handler = move(handler)]( + mtpRequestId requestId, + RPCError &&error) mutable { + std::move(handler)(); + }); + return *this; +} +#else // _DEBUG template template [[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::done( @@ -313,6 +422,8 @@ template return *this; } +#endif // _DEBUG + template [[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder::handleFloodErrors() noexcept -> SpecificRequestBuilder & { diff --git a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp index 90bcb4d8cd3151..1b1c59c6841630 100644 --- a/Telegram/SourceFiles/ui/widgets/separate_panel.cpp +++ b/Telegram/SourceFiles/ui/widgets/separate_panel.cpp @@ -206,7 +206,11 @@ void SeparatePanel::showControls() { void SeparatePanel::finishClose() { hide(); - _closeEvents.fire({}); + crl::on_main(this, [=] { + if (isHidden() && !_visible && !_opacityAnimation.animating()) { + _closeEvents.fire({}); + } + }); } int SeparatePanel::hideGetDuration() { diff --git a/Telegram/gyp/lib_export.gyp b/Telegram/gyp/lib_export.gyp index 138dd8d215ca48..1205920347dab3 100644 --- a/Telegram/gyp/lib_export.gyp +++ b/Telegram/gyp/lib_export.gyp @@ -53,6 +53,14 @@ '<(src_loc)/export/export_controller.cpp', '<(src_loc)/export/export_controller.h', '<(src_loc)/export/export_settings.h', + '<(src_loc)/export/data/export_data_types.cpp', + '<(src_loc)/export/data/export_data_types.h', + '<(src_loc)/export/output/export_output_abstract.cpp', + '<(src_loc)/export/output/export_output_abstract.h', + '<(src_loc)/export/output/export_output_file.cpp', + '<(src_loc)/export/output/export_output_file.h', + '<(src_loc)/export/output/export_output_text.cpp', + '<(src_loc)/export/output/export_output_text.h', ], }], }