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 <QtCore/QDateTime>
namespace App { // Hackish..
QString formatPhone(QString phone);
} // namespace App
namespace Export {
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) {
return data.v;
@ -85,7 +117,7 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) {
case mtpc_photo: {
const auto &photo = data.c_photo();
result.id = photo.vid.v;
result.date = QDateTime::fromTime_t(photo.vdate.v);
result.date = photo.vdate.v;
result.image = ParseMaxImage(photo.vsizes, suggestedPath);
} break;
@ -100,18 +132,19 @@ Photo ParsePhoto(const MTPPhoto &data, const QString &suggestedPath) {
}
Utf8String FormatDateTime(
const QDateTime &date,
TimeId date,
QChar dateSeparator,
QChar timeSeparator,
QChar separator) {
const auto value = QDateTime::fromTime_t(date);
return (QString("%1") + dateSeparator + "%2" + dateSeparator + "%3"
+ separator + "%4" + timeSeparator + "%5" + timeSeparator + "%6"
).arg(date.date().year()
).arg(date.date().month(), 2, 10, QChar('0')
).arg(date.date().day(), 2, 10, QChar('0')
).arg(date.time().hour(), 2, 10, QChar('0')
).arg(date.time().minute(), 2, 10, QChar('0')
).arg(date.time().second(), 2, 10, QChar('0')
).arg(value.date().year()
).arg(value.date().month(), 2, 10, QChar('0')
).arg(value.date().day(), 2, 10, QChar('0')
).arg(value.time().hour(), 2, 10, QChar('0')
).arg(value.time().minute(), 2, 10, QChar('0')
).arg(value.time().second(), 2, 10, QChar('0')
).toUtf8();
}
@ -149,11 +182,17 @@ User ParseUser(const MTPUser &data) {
if (fields.has_username()) {
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;
case mtpc_userEmpty: {
const auto &fields = data.c_userEmpty();
result.id = fields.vid.v;
result.input = MTP_inputUserEmpty();
} break;
default: Unexpected("Type in ParseUser.");
@ -161,8 +200,8 @@ User ParseUser(const MTPUser &data) {
return result;
}
std::map<int, User> ParseUsersList(const MTPVector<MTPUser> &data) {
auto result = std::map<int, User>();
std::map<int32, User> ParseUsersList(const MTPVector<MTPUser> &data) {
auto result = std::map<int32, User>();
for (const auto &user : data.v) {
auto parsed = ParseUser(user);
result.emplace(parsed.id, std::move(parsed));
@ -170,6 +209,152 @@ std::map<int, User> ParseUsersList(const MTPVector<MTPUser> &data) {
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) {
Expects(data.type() == mtpc_userFull);
@ -188,6 +373,7 @@ ContactsList ParseContactsList(const MTPcontacts_Contacts &data) {
auto result = ContactsList();
const auto &contacts = data.c_contacts_contacts();
const auto map = ParseUsersList(contacts.vusers);
result.list.reserve(contacts.vcontacts.v.size());
for (const auto &contact : contacts.vcontacts.v) {
const auto userId = contact.c_contact().vuser_id.v;
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.applicationName = ParseString(fields.vapp_name);
result.applicationVersion = ParseString(fields.vapp_version);
result.created = QDateTime::fromTime_t(fields.vdate_created.v);
result.lastActive = QDateTime::fromTime_t(fields.vdate_active.v);
result.created = fields.vdate_created.v;
result.lastActive = fields.vdate_active.v;
result.ip = ParseString(fields.vip);
result.country = ParseString(fields.vcountry);
result.region = ParseString(fields.vregion);
@ -239,12 +425,62 @@ SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) {
auto result = SessionsList();
const auto &list = data.c_account_authorizations().vauthorizations.v;
result.list.reserve(list.size());
for (const auto &session : list) {
result.list.push_back(ParseSession(session));
}
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) {
return phoneNumber.isEmpty()
? Utf8String()

View File

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

View File

@ -16,10 +16,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export {
namespace {
constexpr auto kUserpicsSliceLimit = 2;
constexpr auto kUserpicsSliceLimit = 100;
constexpr auto kFileChunkSize = 128 * 1024;
constexpr auto kFileRequestsCount = 2;
constexpr auto kFileNextRequestDelay = TimeMs(20);
constexpr auto kChatsSliceLimit = 200;
} // 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) {
}
@ -251,6 +263,49 @@ void ApiWrap::requestSessions(FnMut<void(Data::SessionsList&&)> done) {
}).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) {
Expects(_fileProcess == nullptr);

View File

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

View File

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

View File

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

View File

@ -16,8 +16,8 @@ struct UserpicsInfo;
struct UserpicsSlice;
struct ContactsList;
struct SessionsList;
struct ChatsInfo;
struct ChatInfo;
struct DialogsInfo;
struct DialogInfo;
struct MessagesSlice;
} // namespace Data
@ -43,11 +43,11 @@ public:
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 writeDialogsStart(const Data::DialogsInfo &data) = 0;
virtual bool writeDialogStart(const Data::DialogInfo &data) = 0;
virtual bool writeMessagesSlice(const Data::MessagesSlice &data) = 0;
virtual bool writeChatEnd() = 0;
virtual bool writeChatsEnd() = 0;
virtual bool writeDialogEnd() = 0;
virtual bool writeDialogsEnd() = 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) {
auto lines = QByteArray();
for (const auto &userpic : data.list) {
if (!userpic.date.isValid()) {
if (!userpic.date) {
lines.append("(empty photo)");
} else {
lines.append(Data::FormatDateTime(userpic.date)).append(" - ");
@ -153,10 +153,7 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
return true;
}
const auto header = "Contacts "
"(" + Data::NumberToString(data.list.size()) + ")"
+ kLineBreak
+ kLineBreak;
const auto file = std::make_unique<File>(_folder + "contacts.txt");
auto list = std::vector<QByteArray>();
list.reserve(data.list.size());
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;
return _result->writeBlock(full) == File::Result::Success;
const auto full = JoinList(kLineBreak, list);
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) {
const auto header = "Sessions "
"(" + Data::NumberToString(data.list.size()) + ")"
+ kLineBreak
+ kLineBreak;
if (data.list.empty()) {
return true;
}
const auto file = std::make_unique<File>(_folder + "sessions.txt");
auto list = std::vector<QByteArray>();
list.reserve(data.list.size());
for (const auto &session : data.list) {
@ -208,15 +214,65 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
{ "Created", Data::FormatDateTime(session.created) },
}));
}
const auto full = header + JoinList(kLineBreak, list) + kLineBreak;
return _result->writeBlock(full) == File::Result::Success;
const auto full = JoinList(kLineBreak, list);
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) {
return true;
bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
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;
}
@ -224,11 +280,11 @@ bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) {
return true;
}
bool TextWriter::writeChatEnd() {
bool TextWriter::writeDialogEnd() {
return true;
}
bool TextWriter::writeChatsEnd() {
bool TextWriter::writeDialogsEnd() {
return true;
}

View File

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