mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-01 03:50:43 +00:00
Add chats list export.
This commit is contained in:
parent
affe9defb5
commit
6776d88688
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
||||
};
|
||||
|
@ -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() {
|
||||
|
@ -31,7 +31,7 @@ struct ProcessingState {
|
||||
Userpics,
|
||||
Contacts,
|
||||
Sessions,
|
||||
Chats,
|
||||
Dialogs,
|
||||
};
|
||||
enum class Item {
|
||||
Other,
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user