Use abstract export writer for different formats.

This commit is contained in:
John Preston 2018-06-09 14:51:22 +03:00
parent c587c011d2
commit 0a1a5ed70e
17 changed files with 928 additions and 45 deletions

View File

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

View File

@ -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 <QtCore/QDateTime>
#include <QtCore/QString>
#include <QtCore/QByteArray>
#include <vector>
namespace Export {
namespace Data {
using Utf8String = QByteArray;
Utf8String ParseString(const MTPstring &data);
template <typename Type>
inline auto NumberToString(Type value)
-> std::enable_if_t<std::is_arithmetic_v<Type>, 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<Userpic> list;
};
UserpicsSlice ParseUserpicsSlice(const MTPVector<MTPPhoto> &data);
struct Contact {
Utf8String firstName;
Utf8String lastName;
Utf8String phoneNumber;
};
struct ContactsList {
std::vector<Contact> 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<Session> 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<Message> list;
};
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber);
} // namespace Data
} // namespace Export

View File

@ -8,10 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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 @@ public:
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 @@ private:
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 @@ private:
mtpRequestId _passwordRequestId = 0;
std::unique_ptr<Output::AbstractWriter> _writer;
std::vector<ProcessingState::Step> _steps;
int _stepIndex = -1;
MTPInputUser _user = MTP_inputUserSelf();
};
Controller::Controller(crl::weak_on_queue<Controller> 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();
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();
}).fail([=](const RPCError &error) {
apiError(error);
}).send();
}
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() {

View File

@ -28,7 +28,7 @@ struct PasswordCheckState {
struct ProcessingState {
enum class Step {
PersonalInfo,
Avatars,
Userpics,
Contacts,
Sessions,
Chats,

View File

@ -9,7 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QTextStream>
#include <QtCore/QDateTime>
#include <crl/crl.h>
#include <rpl/rpl.h>
#include <vector>
#include <map>
#include "scheme.h"
#include "logs.h"

View File

@ -11,14 +11,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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<Type>;
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<Type>;
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;

View File

@ -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<AbstractWriter> CreateWriter(Format format) {
return std::make_unique<TextWriter>();
}
} // namespace Output
} // namespace Export

View File

@ -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 <QtCore/QString>
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<AbstractWriter> CreateWriter(Format format);
} // namespace Output
} // namespace Export

View File

@ -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 <gsl/gsl_util>
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

View File

@ -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 <QtCore/QFile>
#include <QtCore/QString>
#include <QtCore/QByteArray>
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<QFile> _file;
};
} // namespace Output
} // namespace File

View File

@ -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 <QtCore/QFile>
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<std::pair<QByteArray, QByteArray>> &&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<File>(_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

View File

@ -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<File> _result;
int _userpicsCount = 0;
};
} // namespace Output
} // namespace Export

View File

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

View File

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

View File

@ -89,6 +89,8 @@ public:
template <typename Request>
class SpecificRequestBuilder : public RequestBuilder {
public:
using Response = typename Request::ResponseType;
SpecificRequestBuilder(
const SpecificRequestBuilder &other) = delete;
SpecificRequestBuilder(
@ -102,10 +104,32 @@ public:
ShiftedDcId dcId) noexcept;
[[nodiscard]] SpecificRequestBuilder &afterDelay(
TimeMs ms) noexcept;
#ifdef _DEBUG
// Allow code completion to show response type.
[[nodiscard]] SpecificRequestBuilder &done(Fn<void()> &&handler);
[[nodiscard]] SpecificRequestBuilder &done(Fn<void(
mtpRequestId)> &&handler);
[[nodiscard]] SpecificRequestBuilder &done(Fn<void(
mtpRequestId,
Response &&)> &&handler);
[[nodiscard]] SpecificRequestBuilder &done(Fn<void(
Response &&)> &&handler);
[[nodiscard]] SpecificRequestBuilder &fail(Fn<void()> &&handler);
[[nodiscard]] SpecificRequestBuilder &fail(Fn<void(
mtpRequestId)> &&handler);
[[nodiscard]] SpecificRequestBuilder &fail(Fn<void(
mtpRequestId,
RPCError &&)> &&handler);
[[nodiscard]] SpecificRequestBuilder &fail(Fn<void(
RPCError &&)> &&handler);
#else // _DEBUG
template <typename Handler>
[[nodiscard]] SpecificRequestBuilder &done(Handler &&handler);
template <typename Handler>
[[nodiscard]] SpecificRequestBuilder &fail(Handler &&handler);
#endif // _DEBUG
[[nodiscard]] SpecificRequestBuilder &handleFloodErrors() noexcept;
[[nodiscard]] SpecificRequestBuilder &handleAllErrors() noexcept;
[[nodiscard]] SpecificRequestBuilder &afterRequest(
@ -228,6 +252,91 @@ template <typename Request>
return *this;
}
#ifdef _DEBUG
// Allow code completion to show response type.
template <typename Request>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
Fn<void(Response &&)> &&handler)
-> SpecificRequestBuilder & {
setDoneHandler<Response>([handler = std::move(handler)](
mtpRequestId requestId,
Response &&result) mutable {
std::move(handler)(std::move(result));
});
return *this;
}
template <typename Request>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
Fn<void(mtpRequestId, Response &&)> &&handler)
-> SpecificRequestBuilder & {
setDoneHandler<Response>(std::move(handler));
return *this;
}
template <typename Request>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
Fn<void(mtpRequestId)> &&handler) -> SpecificRequestBuilder & {
setDoneHandler<Response>([handler = std::move(handler)](
mtpRequestId requestId,
Response &&result) mutable {
std::move(handler)(requestId);
});
return *this;
}
template <typename Request>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
Fn<void()> &&handler) -> SpecificRequestBuilder & {
setDoneHandler<Response>([handler = std::move(handler)](
mtpRequestId requestId,
Response &&result) mutable {
std::move(handler)();
});
return *this;
}
template <typename Request>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
Fn<void(RPCError &&)> &&handler) -> SpecificRequestBuilder & {
setFailHandler([handler = std::move(handler)](
mtpRequestId requestId,
RPCError &&error) mutable {
std::move(handler)(std::move(error));
});
return *this;
}
template <typename Request>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
Fn<void(mtpRequestId, RPCError &&)> &&handler)
-> SpecificRequestBuilder & {
setFailHandler(std::move(handler));
return *this;
}
template <typename Request>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
Fn<void(mtpRequestId)> &&handler) -> SpecificRequestBuilder & {
setFailHandler([handler = std::move(handler)](
mtpRequestId requestId,
RPCError &&error) mutable {
std::move(handler)(requestId);
});
return *this;
}
template <typename Request>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
Fn<void()> &&handler) -> SpecificRequestBuilder & {
setFailHandler([handler = move(handler)](
mtpRequestId requestId,
RPCError &&error) mutable {
std::move(handler)();
});
return *this;
}
#else // _DEBUG
template <typename Request>
template <typename Handler>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
@ -313,6 +422,8 @@ template <typename Handler>
return *this;
}
#endif // _DEBUG
template <typename Request>
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::handleFloodErrors() noexcept
-> SpecificRequestBuilder & {

View File

@ -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() {

View File

@ -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',
],
}],
}