mirror of https://github.com/procxx/kepka.git
Use abstract export writer for different formats.
This commit is contained in:
parent
c587c011d2
commit
0a1a5ed70e
|
@ -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
|
|
@ -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
|
|
@ -8,10 +8,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "export/export_controller.h"
|
#include "export/export_controller.h"
|
||||||
|
|
||||||
#include "export/export_settings.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/rpc_sender.h"
|
||||||
#include "mtproto/concurrent_sender.h"
|
#include "mtproto/concurrent_sender.h"
|
||||||
|
|
||||||
namespace Export {
|
namespace Export {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kUserpicsSliceLimit = 100;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
class Controller {
|
class Controller {
|
||||||
public:
|
public:
|
||||||
|
@ -30,6 +37,8 @@ public:
|
||||||
void startExport(const Settings &settings);
|
void startExport(const Settings &settings);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using Step = ProcessingState::Step;
|
||||||
|
|
||||||
void setState(State &&state);
|
void setState(State &&state);
|
||||||
void apiError(const RPCError &error);
|
void apiError(const RPCError &error);
|
||||||
void apiError(const QString &error);
|
void apiError(const QString &error);
|
||||||
|
@ -39,6 +48,18 @@ private:
|
||||||
void requestPasswordState();
|
void requestPasswordState();
|
||||||
void passwordStateDone(const MTPaccount_Password &password);
|
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;
|
MTP::ConcurrentSender _mtp;
|
||||||
Settings _settings;
|
Settings _settings;
|
||||||
|
|
||||||
|
@ -48,6 +69,12 @@ private:
|
||||||
|
|
||||||
mtpRequestId _passwordRequestId = 0;
|
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)
|
Controller::Controller(crl::weak_on_queue<Controller> weak)
|
||||||
|
@ -130,47 +157,197 @@ void Controller::cancelUnconfirmedPassword() {
|
||||||
|
|
||||||
void Controller::startExport(const Settings &settings) {
|
void Controller::startExport(const Settings &settings) {
|
||||||
_settings = base::duplicate(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.request(MTPusers_GetFullUser(
|
||||||
MTP_inputUserSelf()
|
_user
|
||||||
)).done([=](const MTPUserFull &result) {
|
)).done([=](const MTPUserFull &result) {
|
||||||
Expects(result.type() == mtpc_userFull);
|
Expects(result.type() == mtpc_userFull);
|
||||||
|
|
||||||
const auto &full = result.c_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.");
|
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) {
|
}).fail([=](const RPCError &error) {
|
||||||
apiError(error);
|
apiError(error);
|
||||||
}).send();
|
}).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() {
|
void Controller::setFinishedState() {
|
||||||
setState(FinishedState{ _settings.path });
|
setState(FinishedState{ _writer->mainFilePath() });
|
||||||
}
|
}
|
||||||
|
|
||||||
ControllerWrap::ControllerWrap() {
|
ControllerWrap::ControllerWrap() {
|
||||||
|
|
|
@ -28,7 +28,7 @@ struct PasswordCheckState {
|
||||||
struct ProcessingState {
|
struct ProcessingState {
|
||||||
enum class Step {
|
enum class Step {
|
||||||
PersonalInfo,
|
PersonalInfo,
|
||||||
Avatars,
|
Userpics,
|
||||||
Contacts,
|
Contacts,
|
||||||
Sessions,
|
Sessions,
|
||||||
Chats,
|
Chats,
|
||||||
|
|
|
@ -9,7 +9,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <QtCore/QFile>
|
#include <QtCore/QFile>
|
||||||
#include <QtCore/QDir>
|
#include <QtCore/QDir>
|
||||||
#include <QtCore/QTextStream>
|
#include <QtCore/QTextStream>
|
||||||
|
#include <QtCore/QDateTime>
|
||||||
#include <crl/crl.h>
|
#include <crl/crl.h>
|
||||||
#include <rpl/rpl.h>
|
#include <rpl/rpl.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
#include "scheme.h"
|
#include "scheme.h"
|
||||||
#include "logs.h"
|
#include "logs.h"
|
||||||
|
|
|
@ -11,14 +11,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "base/flat_map.h"
|
#include "base/flat_map.h"
|
||||||
|
|
||||||
namespace Export {
|
namespace Export {
|
||||||
|
namespace Output {
|
||||||
|
enum class Format;
|
||||||
|
} // namespace Output
|
||||||
|
|
||||||
struct MediaSettings {
|
struct MediaSettings {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
Photo,
|
Photo = 0x01,
|
||||||
Video,
|
Video = 0x02,
|
||||||
Sticker,
|
Sticker = 0x04,
|
||||||
GIF,
|
GIF = 0x08,
|
||||||
File,
|
File = 0x10,
|
||||||
};
|
};
|
||||||
using Types = base::flags<Type>;
|
using Types = base::flags<Type>;
|
||||||
friend inline constexpr auto is_flag_type(Type) { return true; };
|
friend inline constexpr auto is_flag_type(Type) { return true; };
|
||||||
|
@ -34,19 +37,20 @@ struct MediaSettings {
|
||||||
|
|
||||||
struct Settings {
|
struct Settings {
|
||||||
enum class Type {
|
enum class Type {
|
||||||
PersonalInfo,
|
PersonalInfo = 0x01,
|
||||||
Avatars,
|
Userpics = 0x02,
|
||||||
Contacts,
|
Contacts = 0x04,
|
||||||
Sessions,
|
Sessions = 0x08,
|
||||||
PersonalChats,
|
PersonalChats = 0x10,
|
||||||
PrivateGroups,
|
PrivateGroups = 0x20,
|
||||||
PublicGroups,
|
PublicGroups = 0x40,
|
||||||
MyChannels,
|
MyChannels = 0x80,
|
||||||
};
|
};
|
||||||
using Types = base::flags<Type>;
|
using Types = base::flags<Type>;
|
||||||
friend inline constexpr auto is_flag_type(Type) { return true; };
|
friend inline constexpr auto is_flag_type(Type) { return true; };
|
||||||
|
|
||||||
QString path;
|
QString path;
|
||||||
|
Output::Format format = Output::Format();
|
||||||
|
|
||||||
Types types = DefaultTypes();
|
Types types = DefaultTypes();
|
||||||
MediaSettings defaultMedia;
|
MediaSettings defaultMedia;
|
||||||
|
@ -54,7 +58,7 @@ struct Settings {
|
||||||
|
|
||||||
static inline Types DefaultTypes() {
|
static inline Types DefaultTypes() {
|
||||||
return Type::PersonalInfo
|
return Type::PersonalInfo
|
||||||
| Type::Avatars
|
| Type::Userpics
|
||||||
| Type::Contacts
|
| Type::Contacts
|
||||||
| Type::Sessions
|
| Type::Sessions
|
||||||
| Type::PersonalChats;
|
| Type::PersonalChats;
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -74,7 +74,7 @@ void PanelController::updateState(State &&state) {
|
||||||
|
|
||||||
done->showClicks(
|
done->showClicks(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
File::ShowInFolder(path + "personal.txt");
|
File::ShowInFolder(path);
|
||||||
_panel->hideGetDuration();
|
_panel->hideGetDuration();
|
||||||
}, done->lifetime());
|
}, done->lifetime());
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ void SettingsWidget::setupContent() {
|
||||||
refreshButtonsCallback();
|
refreshButtonsCallback();
|
||||||
}, lifetime());
|
}, 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_contacts, Type::Contacts);
|
||||||
addOption(lng_export_option_sessions, Type::Sessions);
|
addOption(lng_export_option_sessions, Type::Sessions);
|
||||||
refreshButtonsCallback();
|
refreshButtonsCallback();
|
||||||
|
|
|
@ -89,6 +89,8 @@ public:
|
||||||
template <typename Request>
|
template <typename Request>
|
||||||
class SpecificRequestBuilder : public RequestBuilder {
|
class SpecificRequestBuilder : public RequestBuilder {
|
||||||
public:
|
public:
|
||||||
|
using Response = typename Request::ResponseType;
|
||||||
|
|
||||||
SpecificRequestBuilder(
|
SpecificRequestBuilder(
|
||||||
const SpecificRequestBuilder &other) = delete;
|
const SpecificRequestBuilder &other) = delete;
|
||||||
SpecificRequestBuilder(
|
SpecificRequestBuilder(
|
||||||
|
@ -102,10 +104,32 @@ public:
|
||||||
ShiftedDcId dcId) noexcept;
|
ShiftedDcId dcId) noexcept;
|
||||||
[[nodiscard]] SpecificRequestBuilder &afterDelay(
|
[[nodiscard]] SpecificRequestBuilder &afterDelay(
|
||||||
TimeMs ms) noexcept;
|
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>
|
template <typename Handler>
|
||||||
[[nodiscard]] SpecificRequestBuilder &done(Handler &&handler);
|
[[nodiscard]] SpecificRequestBuilder &done(Handler &&handler);
|
||||||
template <typename Handler>
|
template <typename Handler>
|
||||||
[[nodiscard]] SpecificRequestBuilder &fail(Handler &&handler);
|
[[nodiscard]] SpecificRequestBuilder &fail(Handler &&handler);
|
||||||
|
#endif // _DEBUG
|
||||||
|
|
||||||
[[nodiscard]] SpecificRequestBuilder &handleFloodErrors() noexcept;
|
[[nodiscard]] SpecificRequestBuilder &handleFloodErrors() noexcept;
|
||||||
[[nodiscard]] SpecificRequestBuilder &handleAllErrors() noexcept;
|
[[nodiscard]] SpecificRequestBuilder &handleAllErrors() noexcept;
|
||||||
[[nodiscard]] SpecificRequestBuilder &afterRequest(
|
[[nodiscard]] SpecificRequestBuilder &afterRequest(
|
||||||
|
@ -228,6 +252,91 @@ template <typename Request>
|
||||||
return *this;
|
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 Request>
|
||||||
template <typename Handler>
|
template <typename Handler>
|
||||||
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
|
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
|
||||||
|
@ -313,6 +422,8 @@ template <typename Handler>
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif // _DEBUG
|
||||||
|
|
||||||
template <typename Request>
|
template <typename Request>
|
||||||
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::handleFloodErrors() noexcept
|
[[nodiscard]] auto ConcurrentSender::SpecificRequestBuilder<Request>::handleFloodErrors() noexcept
|
||||||
-> SpecificRequestBuilder & {
|
-> SpecificRequestBuilder & {
|
||||||
|
|
|
@ -206,7 +206,11 @@ void SeparatePanel::showControls() {
|
||||||
|
|
||||||
void SeparatePanel::finishClose() {
|
void SeparatePanel::finishClose() {
|
||||||
hide();
|
hide();
|
||||||
_closeEvents.fire({});
|
crl::on_main(this, [=] {
|
||||||
|
if (isHidden() && !_visible && !_opacityAnimation.animating()) {
|
||||||
|
_closeEvents.fire({});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int SeparatePanel::hideGetDuration() {
|
int SeparatePanel::hideGetDuration() {
|
||||||
|
|
|
@ -53,6 +53,14 @@
|
||||||
'<(src_loc)/export/export_controller.cpp',
|
'<(src_loc)/export/export_controller.cpp',
|
||||||
'<(src_loc)/export/export_controller.h',
|
'<(src_loc)/export/export_controller.h',
|
||||||
'<(src_loc)/export/export_settings.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',
|
||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue