Add chats list export.

This commit is contained in:
John Preston 2018-06-12 21:09:21 +03:00
parent affe9defb5
commit 6776d88688
9 changed files with 473 additions and 77 deletions

View File

@ -7,12 +7,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "export/data/export_data_types.h" #include "export/data/export_data_types.h"
#include <QtCore/QDateTime>
namespace App { // Hackish.. namespace App { // Hackish..
QString formatPhone(QString phone); QString formatPhone(QString phone);
} // namespace App } // namespace App
namespace Export { namespace Export {
namespace Data { namespace Data {
namespace {
constexpr auto kUserPeerIdShift = (1ULL << 32);
constexpr auto kChatPeerIdShift = (2ULL << 32);
} // namespace
PeerId UserPeerId(int32 userId) {
return kUserPeerIdShift | uint32(userId);
}
PeerId ChatPeerId(int32 chatId) {
return kChatPeerIdShift | uint32(chatId);
}
int32 BarePeerId(PeerId peerId) {
return int32(peerId & 0xFFFFFFFFULL);
}
PeerId ParsePeerId(const MTPPeer &data) {
switch (data.type()) {
case mtpc_peerUser:
return UserPeerId(data.c_peerUser().vuser_id.v);
case mtpc_peerChat:
return ChatPeerId(data.c_peerChat().vchat_id.v);
case mtpc_peerChannel:
return ChatPeerId(data.c_peerChannel().vchannel_id.v);
}
Unexpected("Type in ParsePeerId.");
}
Utf8String ParseString(const MTPstring &data) { Utf8String ParseString(const MTPstring &data) {
return data.v; return data.v;
@ -85,7 +117,7 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) {
case mtpc_photo: { case mtpc_photo: {
const auto &photo = data.c_photo(); const auto &photo = data.c_photo();
result.id = photo.vid.v; result.id = photo.vid.v;
result.date = QDateTime::fromTime_t(photo.vdate.v); result.date = photo.vdate.v;
result.image = ParseMaxImage(photo.vsizes, suggestedPath); result.image = ParseMaxImage(photo.vsizes, suggestedPath);
} break; } break;
@ -100,18 +132,19 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) {
} }
Utf8String FormatDateTime( Utf8String FormatDateTime(
const QDateTime &date, TimeId date,
QChar dateSeparator, QChar dateSeparator,
QChar timeSeparator, QChar timeSeparator,
QChar separator) { QChar separator) {
const auto value = QDateTime::fromTime_t(date);
return (QString("%1") + dateSeparator + "%2" + dateSeparator + "%3" return (QString("%1") + dateSeparator + "%2" + dateSeparator + "%3"
+ separator + "%4" + timeSeparator + "%5" + timeSeparator + "%6" + separator + "%4" + timeSeparator + "%5" + timeSeparator + "%6"
).arg(date.date().year() ).arg(value.date().year()
).arg(date.date().month(), 2, 10, QChar('0') ).arg(value.date().month(), 2, 10, QChar('0')
).arg(date.date().day(), 2, 10, QChar('0') ).arg(value.date().day(), 2, 10, QChar('0')
).arg(date.time().hour(), 2, 10, QChar('0') ).arg(value.time().hour(), 2, 10, QChar('0')
).arg(date.time().minute(), 2, 10, QChar('0') ).arg(value.time().minute(), 2, 10, QChar('0')
).arg(date.time().second(), 2, 10, QChar('0') ).arg(value.time().second(), 2, 10, QChar('0')
).toUtf8(); ).toUtf8();
} }
@ -149,11 +182,17 @@ User ParseUser(const MTPUser &data) {
if (fields.has_username()) { if (fields.has_username()) {
result.username = ParseString(fields.vusername); result.username = ParseString(fields.vusername);
} }
if (fields.has_access_hash()) {
result.input = MTP_inputUser(fields.vid, fields.vaccess_hash);
} else {
result.input = MTP_inputUserEmpty();
}
} break; } break;
case mtpc_userEmpty: { case mtpc_userEmpty: {
const auto &fields = data.c_userEmpty(); const auto &fields = data.c_userEmpty();
result.id = fields.vid.v; result.id = fields.vid.v;
result.input = MTP_inputUserEmpty();
} break; } break;
default: Unexpected("Type in ParseUser."); default: Unexpected("Type in ParseUser.");
@ -161,8 +200,8 @@ User ParseUser(const MTPUser &data) {
return result; return result;
} }
std::map<int, User> ParseUsersList(const MTPVector<MTPUser> &data) { std::map<int32, User> ParseUsersList(const MTPVector<MTPUser> &data) {
auto result = std::map<int, User>(); auto result = std::map<int32, User>();
for (const auto &user : data.v) { for (const auto &user : data.v) {
auto parsed = ParseUser(user); auto parsed = ParseUser(user);
result.emplace(parsed.id, std::move(parsed)); result.emplace(parsed.id, std::move(parsed));
@ -170,6 +209,152 @@ std::map<int, User> ParseUsersList(const MTPVector<MTPUser> &data) {
return result; return result;
} }
Chat ParseChat(const MTPChat &data) {
auto result = Chat();
switch (data.type()) {
case mtpc_chat: {
const auto &fields = data.c_chat();
result.id = fields.vid.v;
result.title = ParseString(fields.vtitle);
result.input = MTP_inputPeerChat(MTP_int(result.id));
} break;
case mtpc_chatEmpty: {
const auto &fields = data.c_chatEmpty();
result.id = fields.vid.v;
result.input = MTP_inputPeerChat(MTP_int(result.id));
} break;
case mtpc_chatForbidden: {
const auto &fields = data.c_chatForbidden();
result.id = fields.vid.v;
result.title = ParseString(fields.vtitle);
result.input = MTP_inputPeerChat(MTP_int(result.id));
} break;
case mtpc_channel: {
const auto &fields = data.c_channel();
result.id = fields.vid.v;
result.broadcast = fields.is_broadcast();
result.title = ParseString(fields.vtitle);
if (fields.has_username()) {
result.username = ParseString(fields.vusername);
}
result.input = MTP_inputPeerChannel(
MTP_int(result.id),
fields.vaccess_hash);
} break;
case mtpc_channelForbidden: {
const auto &fields = data.c_channelForbidden();
result.id = fields.vid.v;
result.broadcast = fields.is_broadcast();
result.title = ParseString(fields.vtitle);
result.input = MTP_inputPeerChannel(
MTP_int(result.id),
fields.vaccess_hash);
} break;
default: Unexpected("Type in ParseChat.");
}
return result;
}
std::map<int32, Chat> ParseChatsList(const MTPVector<MTPChat> &data) {
auto result = std::map<int32, Chat>();
for (const auto &chat : data.v) {
auto parsed = ParseChat(chat);
result.emplace(parsed.id, std::move(parsed));
}
return result;
}
const User *Peer::user() const {
return base::get_if<User>(&data);
}
const Chat *Peer::chat() const {
return base::get_if<Chat>(&data);
}
PeerId Peer::id() const {
if (const auto user = this->user()) {
return UserPeerId(user->id);
} else if (const auto chat = this->chat()) {
return ChatPeerId(chat->id);
}
Unexpected("Variant in Peer::id.");
}
Utf8String Peer::name() const {
if (const auto user = this->user()) {
return user->firstName + ' ' + user->lastName;
} else if (const auto chat = this->chat()) {
return chat->title;
}
Unexpected("Variant in Peer::id.");
}
MTPInputPeer Peer::input() const {
if (const auto user = this->user()) {
if (user->input.type() == mtpc_inputUser) {
const auto &input = user->input.c_inputUser();
return MTP_inputPeerUser(input.vuser_id, input.vaccess_hash);
}
return MTP_inputPeerEmpty();
} else if (const auto chat = this->chat()) {
return chat->input;
}
Unexpected("Variant in Peer::id.");
}
std::map<PeerId, Peer> ParsePeersLists(
const MTPVector<MTPUser> &users,
const MTPVector<MTPChat> &chats) {
auto result = std::map<PeerId, Peer>();
for (const auto &user : users.v) {
auto parsed = ParseUser(user);
result.emplace(UserPeerId(parsed.id), Peer{ std::move(parsed) });
}
for (const auto &chat : chats.v) {
auto parsed = ParseChat(chat);
result.emplace(ChatPeerId(parsed.id), Peer{ std::move(parsed) });
}
return result;
}
Message ParseMessage(const MTPMessage &data) {
auto result = Message();
switch (data.type()) {
case mtpc_message: {
const auto &fields = data.c_message();
result.id = fields.vid.v;
result.date = fields.vdate.v;
} break;
case mtpc_messageService: {
const auto &fields = data.c_messageService();
result.id = fields.vid.v;
result.date = fields.vdate.v;
} break;
case mtpc_messageEmpty: {
const auto &fields = data.c_messageEmpty();
result.id = fields.vid.v;
} break;
}
return result;
}
std::map<int32, Message> ParseMessagesList(
const MTPVector<MTPMessage> &data) {
auto result = std::map<int32, Message>();
for (const auto &message : data.v) {
auto parsed = ParseMessage(message);
result.emplace(parsed.id, std::move(parsed));
}
return result;
}
PersonalInfo ParsePersonalInfo(const MTPUserFull &data) { PersonalInfo ParsePersonalInfo(const MTPUserFull &data) {
Expects(data.type() == mtpc_userFull); Expects(data.type() == mtpc_userFull);
@ -188,6 +373,7 @@ ContactsList ParseContactsList(const MTPcontacts_Contacts &data) {
auto result = ContactsList(); auto result = ContactsList();
const auto &contacts = data.c_contacts_contacts(); const auto &contacts = data.c_contacts_contacts();
const auto map = ParseUsersList(contacts.vusers); const auto map = ParseUsersList(contacts.vusers);
result.list.reserve(contacts.vcontacts.v.size());
for (const auto &contact : contacts.vcontacts.v) { for (const auto &contact : contacts.vcontacts.v) {
const auto userId = contact.c_contact().vuser_id.v; const auto userId = contact.c_contact().vuser_id.v;
if (const auto i = map.find(userId); i != end(map)) { if (const auto i = map.find(userId); i != end(map)) {
@ -226,8 +412,8 @@ Session ParseSession(const MTPAuthorization &data) {
result.systemVersion = ParseString(fields.vsystem_version); result.systemVersion = ParseString(fields.vsystem_version);
result.applicationName = ParseString(fields.vapp_name); result.applicationName = ParseString(fields.vapp_name);
result.applicationVersion = ParseString(fields.vapp_version); result.applicationVersion = ParseString(fields.vapp_version);
result.created = QDateTime::fromTime_t(fields.vdate_created.v); result.created = fields.vdate_created.v;
result.lastActive = QDateTime::fromTime_t(fields.vdate_active.v); result.lastActive = fields.vdate_active.v;
result.ip = ParseString(fields.vip); result.ip = ParseString(fields.vip);
result.country = ParseString(fields.vcountry); result.country = ParseString(fields.vcountry);
result.region = ParseString(fields.vregion); result.region = ParseString(fields.vregion);
@ -239,12 +425,62 @@ SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) {
auto result = SessionsList(); auto result = SessionsList();
const auto &list = data.c_account_authorizations().vauthorizations.v; const auto &list = data.c_account_authorizations().vauthorizations.v;
result.list.reserve(list.size());
for (const auto &session : list) { for (const auto &session : list) {
result.list.push_back(ParseSession(session)); result.list.push_back(ParseSession(session));
} }
return result; return result;
} }
void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data) {
// const auto process = [&](const MTPDmessages_dialogs &data) {
const auto process = [&](const auto &data) {
const auto peers = ParsePeersLists(data.vusers, data.vchats);
const auto messages = ParseMessagesList(data.vmessages);
to.list.reserve(to.list.size() + data.vdialogs.v.size());
for (const auto &dialog : data.vdialogs.v) {
if (dialog.type() != mtpc_dialog) {
continue;
}
const auto &fields = dialog.c_dialog();
auto info = DialogInfo();
const auto peerId = ParsePeerId(fields.vpeer);
const auto peerIt = peers.find(peerId);
if (peerIt != end(peers)) {
const auto &peer = peerIt->second;
info.type = peer.user()
? DialogInfo::Type::Personal
: peer.chat()->broadcast
? DialogInfo::Type::Channel
: peer.chat()->username.isEmpty()
? DialogInfo::Type::PrivateGroup
: DialogInfo::Type::PublicGroup;
info.name = peer.name();
info.input = peer.input();
}
info.topMessageId = fields.vtop_message.v;
const auto messageIt = messages.find(info.topMessageId);
if (messageIt != end(messages)) {
const auto &message = messageIt->second;
info.topMessageDate = message.date;
}
to.list.push_back(std::move(info));
}
};
switch (data.type()) {
case mtpc_messages_dialogs:
process(data.c_messages_dialogs());
break;
case mtpc_messages_dialogsSlice:
process(data.c_messages_dialogsSlice());
break;
default: Unexpected("Type in AppendParsedChats.");
}
}
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) { Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) {
return phoneNumber.isEmpty() return phoneNumber.isEmpty()
? Utf8String() ? Utf8String()

View File

@ -9,8 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "scheme.h" #include "scheme.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/variant.h"
#include <QtCore/QDateTime>
#include <QtCore/QString> #include <QtCore/QString>
#include <QtCore/QByteArray> #include <QtCore/QByteArray>
#include <vector> #include <vector>
@ -18,7 +18,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export { namespace Export {
namespace Data { namespace Data {
using TimeId = int32;
using Utf8String = QByteArray; using Utf8String = QByteArray;
using PeerId = uint64;
PeerId UserPeerId(int32 userId);
PeerId ChatPeerId(int32 chatId);
int32 BarePeerId(PeerId peerId);
Utf8String ParseString(const MTPstring &data); Utf8String ParseString(const MTPstring &data);
@ -50,7 +56,7 @@ struct File {
struct Photo { struct Photo {
uint64 id = 0; uint64 id = 0;
QDateTime date; TimeId date = 0;
int width = 0; int width = 0;
int height = 0; int height = 0;
@ -64,15 +70,45 @@ struct UserpicsSlice {
UserpicsSlice ParseUserpicsSlice(const MTPVector<MTPPhoto> &data); UserpicsSlice ParseUserpicsSlice(const MTPVector<MTPPhoto> &data);
struct User { struct User {
int id = 0; int32 id = 0;
Utf8String firstName; Utf8String firstName;
Utf8String lastName; Utf8String lastName;
Utf8String phoneNumber; Utf8String phoneNumber;
Utf8String username; Utf8String username;
MTPInputUser input;
}; };
User ParseUser(const MTPUser &user); User ParseUser(const MTPUser &data);
std::map<int, User> ParseUsersList(const MTPVector<MTPUser> &data); std::map<int32, User> ParseUsersList(const MTPVector<MTPUser> &data);
struct Chat {
int32 id = 0;
Utf8String title;
Utf8String username;
bool broadcast = false;
MTPInputPeer input;
};
Chat ParseChat(const MTPChat &data);
std::map<int32, Chat> ParseChatsList(const MTPVector<MTPChat> &data);
struct Peer {
PeerId id() const;
Utf8String name() const;
MTPInputPeer input() const;
const User *user() const;
const Chat *chat() const;
base::variant<User, Chat> data;
};
std::map<PeerId, Peer> ParsePeersLists(
const MTPVector<MTPUser> &users,
const MTPVector<MTPChat> &chats);
struct PersonalInfo { struct PersonalInfo {
User user; User user;
@ -94,8 +130,8 @@ struct Session {
Utf8String systemVersion; Utf8String systemVersion;
Utf8String applicationName; Utf8String applicationName;
Utf8String applicationVersion; Utf8String applicationVersion;
QDateTime created; TimeId created = 0;
QDateTime lastActive; TimeId lastActive = 0;
Utf8String ip; Utf8String ip;
Utf8String country; Utf8String country;
Utf8String region; Utf8String region;
@ -107,24 +143,38 @@ struct SessionsList {
SessionsList ParseSessionsList(const MTPaccount_Authorizations &data); SessionsList ParseSessionsList(const MTPaccount_Authorizations &data);
struct ChatsInfo { struct Message {
int count = 0; int32 id = 0;
TimeId date = 0;
}; };
struct ChatInfo { Message ParseMessage(const MTPMessage &data);
std::map<int32, Message> ParseMessagesList(
const MTPVector<MTPMessage> &data);
struct DialogInfo {
enum class Type { enum class Type {
Unknown,
Personal, Personal,
Group, PrivateGroup,
PublicGroup,
Channel, Channel,
}; };
Type type = Type::Personal; Type type = Type::Unknown;
QString name; Utf8String name;
MTPInputPeer input;
int32 topMessageId = 0;
TimeId topMessageDate = 0;
}; };
struct Message { struct DialogsInfo {
int id = 0; std::vector<DialogInfo> list;
}; };
void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data);
struct MessagesSlice { struct MessagesSlice {
std::vector<Message> list; std::vector<Message> list;
}; };
@ -132,22 +182,10 @@ struct MessagesSlice {
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber); Utf8String FormatPhoneNumber(const Utf8String &phoneNumber);
Utf8String FormatDateTime( Utf8String FormatDateTime(
const QDateTime &date, TimeId date,
QChar dateSeparator = QChar('.'), QChar dateSeparator = QChar('.'),
QChar timeSeparator = QChar(':'), QChar timeSeparator = QChar(':'),
QChar separator = QChar(' ')); QChar separator = QChar(' '));
inline Utf8String FormatDateTime(
int32 date,
QChar dateSeparator = QChar('.'),
QChar timeSeparator = QChar(':'),
QChar separator = QChar(' ')) {
return FormatDateTime(
QDateTime::fromTime_t(date),
dateSeparator,
timeSeparator,
separator);
}
} // namespace Data } // namespace Data
} // namespace Export } // namespace Export

View File

@ -16,10 +16,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export { namespace Export {
namespace { namespace {
constexpr auto kUserpicsSliceLimit = 2; constexpr auto kUserpicsSliceLimit = 100;
constexpr auto kFileChunkSize = 128 * 1024; constexpr auto kFileChunkSize = 128 * 1024;
constexpr auto kFileRequestsCount = 2; constexpr auto kFileRequestsCount = 2;
constexpr auto kFileNextRequestDelay = TimeMs(20); constexpr auto kFileNextRequestDelay = TimeMs(20);
constexpr auto kChatsSliceLimit = 200;
} // namespace } // namespace
@ -54,6 +55,17 @@ struct ApiWrap::FileProcess {
}; };
struct ApiWrap::DialogsProcess {
Data::DialogsInfo info;
FnMut<void(Data::DialogsInfo&&)> done;
int32 offsetDate = 0;
int32 offsetId = 0;
MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
};
ApiWrap::FileProcess::FileProcess(const QString &path) : file(path) { ApiWrap::FileProcess::FileProcess(const QString &path) : file(path) {
} }
@ -251,6 +263,49 @@ void ApiWrap::requestSessions(FnMut<void(Data::SessionsList&&)> done) {
}).send(); }).send();
} }
void ApiWrap::requestDialogs(FnMut<void(Data::DialogsInfo&&)> done) {
Expects(_dialogsProcess == nullptr);
_dialogsProcess = std::make_unique<DialogsProcess>();
_dialogsProcess->done = std::move(done);
requestDialogsSlice();
}
void ApiWrap::requestDialogsSlice() {
Expects(_dialogsProcess != nullptr);
mainRequest(MTPmessages_GetDialogs(
MTP_flags(0),
MTP_int(_dialogsProcess->offsetDate),
MTP_int(_dialogsProcess->offsetId),
_dialogsProcess->offsetPeer,
MTP_int(kChatsSliceLimit)
)).done([=](const MTPmessages_Dialogs &result) mutable {
const auto finished = [&] {
switch (result.type()) {
case mtpc_messages_dialogs: return true;
case mtpc_messages_dialogsSlice: {
const auto &data = result.c_messages_dialogsSlice();
return data.vdialogs.v.isEmpty();
} break;
default: Unexpected("Type in ApiWrap::requestChatsSlice.");
}
}();
Data::AppendParsedDialogs(_dialogsProcess->info, result);
if (finished || _dialogsProcess->info.list.empty()) {
auto process = base::take(_dialogsProcess);
ranges::reverse(process->info.list);
process->done(std::move(process->info));
} else {
const auto &last = _dialogsProcess->info.list.back();
_dialogsProcess->offsetId = last.topMessageId;
_dialogsProcess->offsetDate = last.topMessageDate;
_dialogsProcess->offsetPeer = last.input;
requestDialogsSlice();
}
}).send();
}
void ApiWrap::loadFile(const Data::File &file, FnMut<void(QString)> done) { void ApiWrap::loadFile(const Data::File &file, FnMut<void(QString)> done) {
Expects(_fileProcess == nullptr); Expects(_fileProcess == nullptr);

View File

@ -18,6 +18,7 @@ struct UserpicsInfo;
struct UserpicsSlice; struct UserpicsSlice;
struct ContactsList; struct ContactsList;
struct SessionsList; struct SessionsList;
struct DialogsInfo;
} // namespace Data } // namespace Data
class ApiWrap { class ApiWrap {
@ -39,6 +40,8 @@ public:
void requestSessions(FnMut<void(Data::SessionsList&&)> done); void requestSessions(FnMut<void(Data::SessionsList&&)> done);
void requestDialogs(FnMut<void(Data::DialogsInfo&&)> done);
~ApiWrap(); ~ApiWrap();
private: private:
@ -48,6 +51,8 @@ private:
void loadUserpicDone(const QString &relativePath); void loadUserpicDone(const QString &relativePath);
void finishUserpics(); void finishUserpics();
void requestDialogsSlice();
void loadFile(const Data::File &file, FnMut<void(QString)> done); void loadFile(const Data::File &file, FnMut<void(QString)> done);
void loadFilePart(); void loadFilePart();
void filePartDone(int offset, const MTPupload_File &result); void filePartDone(int offset, const MTPupload_File &result);
@ -72,6 +77,9 @@ private:
struct FileProcess; struct FileProcess;
std::unique_ptr<FileProcess> _fileProcess; std::unique_ptr<FileProcess> _fileProcess;
struct DialogsProcess;
std::unique_ptr<DialogsProcess> _dialogsProcess;
rpl::event_stream<RPCError> _errors; rpl::event_stream<RPCError> _errors;
}; };

View File

@ -46,7 +46,7 @@ private:
void exportUserpics(); void exportUserpics();
void exportContacts(); void exportContacts();
void exportSessions(); void exportSessions();
void exportChats(); void exportDialogs();
bool normalizePath(); bool normalizePath();
@ -206,12 +206,12 @@ void Controller::fillExportSteps() {
if (_settings.types & Type::Sessions) { if (_settings.types & Type::Sessions) {
_steps.push_back(Step::Sessions); _steps.push_back(Step::Sessions);
} }
const auto chatTypes = Type::PersonalChats const auto dialogTypes = Type::PersonalChats
| Type::PrivateGroups | Type::PrivateGroups
| Type::PublicGroups | Type::PublicGroups
| Type::MyChannels; | Type::MyChannels;
if (_settings.types & chatTypes) { if (_settings.types & dialogTypes) {
_steps.push_back(Step::Chats); _steps.push_back(Step::Dialogs);
} }
} }
@ -230,7 +230,7 @@ void Controller::exportNext() {
case Step::Userpics: return exportUserpics(); case Step::Userpics: return exportUserpics();
case Step::Contacts: return exportContacts(); case Step::Contacts: return exportContacts();
case Step::Sessions: return exportSessions(); case Step::Sessions: return exportSessions();
case Step::Chats: return exportChats(); case Step::Dialogs: return exportDialogs();
} }
Unexpected("Step in Controller::exportNext."); Unexpected("Step in Controller::exportNext.");
} }
@ -267,8 +267,11 @@ void Controller::exportSessions() {
}); });
} }
void Controller::exportChats() { void Controller::exportDialogs() {
exportNext(); _api.requestDialogs([=](Data::DialogsInfo &&result) {
_writer->writeDialogsStart(result);
exportNext();
});
} }
void Controller::setFinishedState() { void Controller::setFinishedState() {

View File

@ -31,7 +31,7 @@ struct ProcessingState {
Userpics, Userpics,
Contacts, Contacts,
Sessions, Sessions,
Chats, Dialogs,
}; };
enum class Item { enum class Item {
Other, Other,

View File

@ -16,8 +16,8 @@ struct UserpicsInfo;
struct UserpicsSlice; struct UserpicsSlice;
struct ContactsList; struct ContactsList;
struct SessionsList; struct SessionsList;
struct ChatsInfo; struct DialogsInfo;
struct ChatInfo; struct DialogInfo;
struct MessagesSlice; struct MessagesSlice;
} // namespace Data } // namespace Data
@ -43,11 +43,11 @@ public:
virtual bool writeSessionsList(const Data::SessionsList &data) = 0; virtual bool writeSessionsList(const Data::SessionsList &data) = 0;
virtual bool writeChatsStart(const Data::ChatsInfo &data) = 0; virtual bool writeDialogsStart(const Data::DialogsInfo &data) = 0;
virtual bool writeChatStart(const Data::ChatInfo &data) = 0; virtual bool writeDialogStart(const Data::DialogInfo &data) = 0;
virtual bool writeMessagesSlice(const Data::MessagesSlice &data) = 0; virtual bool writeMessagesSlice(const Data::MessagesSlice &data) = 0;
virtual bool writeChatEnd() = 0; virtual bool writeDialogEnd() = 0;
virtual bool writeChatsEnd() = 0; virtual bool writeDialogsEnd() = 0;
virtual bool finish() = 0; virtual bool finish() = 0;

View File

@ -127,7 +127,7 @@ bool TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) { bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
auto lines = QByteArray(); auto lines = QByteArray();
for (const auto &userpic : data.list) { for (const auto &userpic : data.list) {
if (!userpic.date.isValid()) { if (!userpic.date) {
lines.append("(empty photo)"); lines.append("(empty photo)");
} else { } else {
lines.append(Data::FormatDateTime(userpic.date)).append(" - "); lines.append(Data::FormatDateTime(userpic.date)).append(" - ");
@ -153,10 +153,7 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
return true; return true;
} }
const auto header = "Contacts " const auto file = std::make_unique<File>(_folder + "contacts.txt");
"(" + Data::NumberToString(data.list.size()) + ")"
+ kLineBreak
+ kLineBreak;
auto list = std::vector<QByteArray>(); auto list = std::vector<QByteArray>();
list.reserve(data.list.size()); list.reserve(data.list.size());
for (const auto &index : Data::SortedContactsIndices(data)) { for (const auto &index : Data::SortedContactsIndices(data)) {
@ -178,15 +175,24 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
})); }));
} }
} }
const auto full = header + JoinList(kLineBreak, list) + kLineBreak; const auto full = JoinList(kLineBreak, list);
return _result->writeBlock(full) == File::Result::Success; if (file->writeBlock(full) != File::Result::Success) {
return false;
}
const auto header = "Contacts "
"(" + Data::NumberToString(data.list.size()) + ") - contacts.txt"
+ kLineBreak
+ kLineBreak;
return _result->writeBlock(header) == File::Result::Success;
} }
bool TextWriter::writeSessionsList(const Data::SessionsList &data) { bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
const auto header = "Sessions " if (data.list.empty()) {
"(" + Data::NumberToString(data.list.size()) + ")" return true;
+ kLineBreak }
+ kLineBreak;
const auto file = std::make_unique<File>(_folder + "sessions.txt");
auto list = std::vector<QByteArray>(); auto list = std::vector<QByteArray>();
list.reserve(data.list.size()); list.reserve(data.list.size());
for (const auto &session : data.list) { for (const auto &session : data.list) {
@ -208,15 +214,65 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
{ "Created", Data::FormatDateTime(session.created) }, { "Created", Data::FormatDateTime(session.created) },
})); }));
} }
const auto full = header + JoinList(kLineBreak, list) + kLineBreak; const auto full = JoinList(kLineBreak, list);
return _result->writeBlock(full) == File::Result::Success; if (file->writeBlock(full) != File::Result::Success) {
return false;
}
const auto header = "Sessions "
"(" + Data::NumberToString(data.list.size()) + ") - sessions.txt"
+ kLineBreak
+ kLineBreak;
return _result->writeBlock(header) == File::Result::Success;
} }
bool TextWriter::writeChatsStart(const Data::ChatsInfo &data) { bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
return true; if (data.list.empty()) {
return true;
}
using Type = Data::DialogInfo::Type;
const auto TypeString = [](Type type) {
switch (type) {
case Type::Unknown: return "(unknown)";
case Type::Personal: return "Personal Chat";
case Type::PrivateGroup: return "Private Group";
case Type::PublicGroup: return "Public Group";
case Type::Channel: return "Channel";
}
Unexpected("Dialog type in TypeString.");
};
const auto digits = Data::NumberToString(data.list.size() - 1).size();
const auto file = std::make_unique<File>(_folder + "chats.txt");
auto list = std::vector<QByteArray>();
list.reserve(data.list.size());
auto index = 0;
for (const auto &dialog : data.list) {
auto number = Data::NumberToString(++index);
auto path = QByteArray("Chats/chat_");
for (auto i = number.size(); i < digits; ++i) {
path += '0';
}
path += number + ".txt";
list.push_back(SerializeKeyValue({
{ "Name", dialog.name },
{ "Type", TypeString(dialog.type) },
{ "Content", path }
}));
}
const auto full = JoinList(kLineBreak, list);
if (file->writeBlock(full) != File::Result::Success) {
return false;
}
const auto header = "Chats "
"(" + Data::NumberToString(data.list.size()) + ") - chats.txt"
+ kLineBreak
+ kLineBreak;
return _result->writeBlock(header) == File::Result::Success;
} }
bool TextWriter::writeChatStart(const Data::ChatInfo &data) { bool TextWriter::writeDialogStart(const Data::DialogInfo &data) {
return true; return true;
} }
@ -224,11 +280,11 @@ bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) {
return true; return true;
} }
bool TextWriter::writeChatEnd() { bool TextWriter::writeDialogEnd() {
return true; return true;
} }
bool TextWriter::writeChatsEnd() { bool TextWriter::writeDialogsEnd() {
return true; return true;
} }

View File

@ -27,11 +27,11 @@ public:
bool writeSessionsList(const Data::SessionsList &data) override; bool writeSessionsList(const Data::SessionsList &data) override;
bool writeChatsStart(const Data::ChatsInfo &data) override; bool writeDialogsStart(const Data::DialogsInfo &data) override;
bool writeChatStart(const Data::ChatInfo &data) override; bool writeDialogStart(const Data::DialogInfo &data) override;
bool writeMessagesSlice(const Data::MessagesSlice &data) override; bool writeMessagesSlice(const Data::MessagesSlice &data) override;
bool writeChatEnd() override; bool writeDialogEnd() override;
bool writeChatsEnd() override; bool writeDialogsEnd() override;
bool finish() override; bool finish() override;