Export chat messages text.

This commit is contained in:
John Preston 2018-06-13 16:12:36 +03:00
parent 35ffc03988
commit 2b36dd660b
10 changed files with 418 additions and 72 deletions

View File

@ -48,6 +48,19 @@ Utf8String ParseString(const MTPstring &data) {
return data.v;
}
Utf8String FillLeft(const Utf8String &data, int length, char filler) {
if (length <= data.size()) {
return data;
}
auto result = Utf8String();
result.reserve(length);
for (auto i = 0, count = length - data.size(); i != count; ++i) {
result.append(filler);
}
result.append(data);
return result;
}
FileLocation ParseLocation(const MTPFileLocation &data) {
return data.visit([](const MTPDfileLocation &data) {
return FileLocation{
@ -158,14 +171,13 @@ User ParseUser(const MTPUser &data) {
if (data.has_username()) {
result.username = ParseString(data.vusername);
}
if (data.has_access_hash()) {
result.input = MTP_inputUser(data.vid, data.vaccess_hash);
} else {
result.input = MTP_inputUserEmpty();
}
const auto access_hash = data.has_access_hash()
? data.vaccess_hash
: MTP_long(0);
result.input = MTP_inputUser(data.vid, access_hash);
}, [&](const MTPDuserEmpty &data) {
result.id = data.vid.v;
result.input = MTP_inputUserEmpty();
result.input = MTP_inputUser(data.vid, MTP_long(0));
});
return result;
}
@ -240,7 +252,13 @@ PeerId Peer::id() const {
Utf8String Peer::name() const {
if (const auto user = this->user()) {
return user->firstName + ' ' + user->lastName;
return user->firstName.isEmpty()
? (user->lastName.isEmpty()
? Utf8String()
: user->lastName)
: (user->lastName.isEmpty()
? user->firstName
: user->firstName + ' ' + user->lastName);
} else if (const auto chat = this->chat()) {
return chat->title;
}
@ -280,6 +298,7 @@ Message ParseMessage(const MTPMessage &data) {
data.visit([&](const MTPDmessage &data) {
result.id = data.vid.v;
result.date = data.vdate.v;
result.text = ParseString(data.vmessage);
}, [&](const MTPDmessageService &data) {
result.id = data.vid.v;
result.date = data.vdate.v;
@ -376,12 +395,12 @@ SessionsList ParseSessionsList(const MTPaccount_Authorizations &data) {
return result;
}
void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data) {
// const auto process = [&](const MTPDmessages_dialogs &data) {
const auto process = [&](const auto &data) {
DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
auto result = DialogsInfo();
data.visit([&](const auto &data) { // MTPDmessages_dialogs &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());
result.list.reserve(result.list.size() + data.vdialogs.v.size());
for (const auto &dialog : data.vdialogs.v) {
if (dialog.type() != mtpc_dialog) {
continue;
@ -409,10 +428,25 @@ void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data) {
const auto &message = messageIt->second;
info.topMessageDate = message.date;
}
to.list.push_back(std::move(info));
result.list.push_back(std::move(info));
}
};
data.visit(process);
});
return result;
}
MessagesSlice ParseMessagesSlice(
const MTPVector<MTPMessage> &data,
const MTPVector<MTPUser> &users,
const MTPVector<MTPChat> &chats) {
const auto &list = data.v;
auto result = MessagesSlice();
result.list.reserve(list.size());
for (const auto &message : list) {
result.list.push_back(ParseMessage(message));
}
ranges::reverse(result.list);
result.peers = ParsePeersLists(users, chats);
return result;
}
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber) {

View File

@ -28,11 +28,16 @@ int32 BarePeerId(PeerId peerId);
Utf8String ParseString(const MTPstring &data);
Utf8String FillLeft(const Utf8String &data, int length, char filler);
template <typename Type>
inline auto NumberToString(Type value)
inline auto NumberToString(Type value, int length = 0, char filler = '0')
-> std::enable_if_t<std::is_arithmetic_v<Type>, Utf8String> {
const auto result = std::to_string(value);
return QByteArray(result.data(), int(result.size()));
return FillLeft(
Utf8String(result.data(), int(result.size())),
length,
filler);
}
struct UserpicsInfo {
@ -147,6 +152,9 @@ struct Message {
int32 id = 0;
TimeId date = 0;
Utf8String text;
File mediaFile;
};
Message ParseMessage(const MTPMessage &data);
@ -173,12 +181,18 @@ struct DialogsInfo {
std::vector<DialogInfo> list;
};
void AppendParsedDialogs(DialogsInfo &to, const MTPmessages_Dialogs &data);
DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data);
struct MessagesSlice {
std::vector<Message> list;
std::map<PeerId, Peer> peers;
};
MessagesSlice ParseMessagesSlice(
const MTPVector<MTPMessage> &data,
const MTPVector<MTPUser> &users,
const MTPVector<MTPChat> &chats);
Utf8String FormatPhoneNumber(const Utf8String &phoneNumber);
Utf8String FormatDateTime(

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "export/export_api_wrap.h"
#include "export/export_settings.h"
#include "export/data/export_data_types.h"
#include "export/output/export_output_file.h"
#include "mtproto/rpc_sender.h"
@ -20,7 +21,8 @@ constexpr auto kUserpicsSliceLimit = 100;
constexpr auto kFileChunkSize = 128 * 1024;
constexpr auto kFileRequestsCount = 2;
constexpr auto kFileNextRequestDelay = TimeMs(20);
constexpr auto kChatsSliceLimit = 200;
constexpr auto kChatsSliceLimit = 100;
constexpr auto kMessagesSliceLimit = 100;
} // namespace
@ -31,7 +33,7 @@ struct ApiWrap::UserpicsProcess {
base::optional<Data::UserpicsSlice> slice;
bool lastSlice = false;
int loading = -1;
int fileIndex = -1;
};
@ -58,17 +60,41 @@ struct ApiWrap::FileProcess {
struct ApiWrap::DialogsProcess {
Data::DialogsInfo info;
FnMut<void(Data::DialogsInfo&&)> done;
FnMut<void(const Data::DialogsInfo&)> start;
Fn<void(const Data::DialogInfo&)> startOne;
Fn<void(Data::MessagesSlice&&)> sliceOne;
Fn<void()> finishOne;
FnMut<void()> finish;
int32 offsetDate = 0;
Data::TimeId offsetDate = 0;
int32 offsetId = 0;
MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
struct Single;
std::unique_ptr<Single> single;
int singleIndex = -1;
};
struct ApiWrap::DialogsProcess::Single {
Single(const Data::DialogInfo &info);
MTPInputPeer peer;
int32 offsetId = 1;
base::optional<Data::MessagesSlice> slice;
bool lastSlice = false;
int fileIndex = -1;
};
ApiWrap::FileProcess::FileProcess(const QString &path) : file(path) {
}
ApiWrap::DialogsProcess::Single::Single(const Data::DialogInfo &info)
: peer(info.input) {
}
template <typename Request>
auto ApiWrap::mainRequest(Request &&request) {
return std::move(_mtp.request(
@ -94,16 +120,16 @@ ApiWrap::ApiWrap(Fn<void(FnMut<void()>)> runner)
: _mtp(std::move(runner)) {
}
void ApiWrap::setFilesBaseFolder(const QString &folder) {
Expects(folder.endsWith('/'));
_filesFolder = folder;
}
rpl::producer<RPCError> ApiWrap::errors() const {
return _errors.events();
}
void ApiWrap::startExport(const Settings &settings) {
Expects(_settings == nullptr);
_settings = std::make_unique<Settings>(settings);
}
void ApiWrap::requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done) {
mainRequest(MTPusers_GetFullUser(
_user
@ -123,6 +149,8 @@ void ApiWrap::requestUserpics(
FnMut<void(Data::UserpicsInfo&&)> start,
Fn<void(Data::UserpicsSlice&&)> slice,
FnMut<void()> finish) {
Expects(_userpicsProcess == nullptr);
_userpicsProcess = std::make_unique<UserpicsProcess>();
_userpicsProcess->start = std::move(start);
_userpicsProcess->handleSlice = std::move(slice);
@ -153,9 +181,6 @@ void ApiWrap::requestUserpics(
void ApiWrap::handleUserpicsSlice(const MTPphotos_Photos &result) {
Expects(_userpicsProcess != nullptr);
if (result.type() == mtpc_photos_photos) {
_userpicsProcess->lastSlice = true;
}
result.visit([&](const auto &data) {
if constexpr (MTPDphotos_photos::Is<decltype(data)>()) {
_userpicsProcess->lastSlice = true;
@ -172,7 +197,7 @@ void ApiWrap::loadUserpicsFiles(Data::UserpicsSlice &&slice) {
_userpicsProcess->lastSlice = true;
}
_userpicsProcess->slice = std::move(slice);
_userpicsProcess->loading = -1;
_userpicsProcess->fileIndex = -1;
loadNextUserpic();
}
@ -181,10 +206,10 @@ void ApiWrap::loadNextUserpic() {
Expects(_userpicsProcess->slice.has_value());
const auto &list = _userpicsProcess->slice->list;
++_userpicsProcess->loading;
if (_userpicsProcess->loading < list.size()) {
++_userpicsProcess->fileIndex;
if (_userpicsProcess->fileIndex < list.size()) {
loadFile(
list[_userpicsProcess->loading].image,
list[_userpicsProcess->fileIndex].image,
[=](const QString &path) { loadUserpicDone(path); });
return;
}
@ -213,11 +238,11 @@ void ApiWrap::loadNextUserpic() {
void ApiWrap::loadUserpicDone(const QString &relativePath) {
Expects(_userpicsProcess != nullptr);
Expects(_userpicsProcess->slice.has_value());
Expects((_userpicsProcess->loading >= 0)
&& (_userpicsProcess->loading
Expects((_userpicsProcess->fileIndex >= 0)
&& (_userpicsProcess->fileIndex
< _userpicsProcess->slice->list.size()));
const auto index = _userpicsProcess->loading;
const auto index = _userpicsProcess->fileIndex;
_userpicsProcess->slice->list[index].image.relativePath = relativePath;
loadNextUserpic();
}
@ -250,11 +275,21 @@ void ApiWrap::requestSessions(FnMut<void(Data::SessionsList&&)> done) {
}).send();
}
void ApiWrap::requestDialogs(FnMut<void(Data::DialogsInfo&&)> done) {
void ApiWrap::requestDialogs(
FnMut<void(const Data::DialogsInfo&)> start,
Fn<void(const Data::DialogInfo&)> startOne,
Fn<void(Data::MessagesSlice&&)> sliceOne,
Fn<void()> finishOne,
FnMut<void()> finish) {
Expects(_dialogsProcess == nullptr);
_dialogsProcess = std::make_unique<DialogsProcess>();
_dialogsProcess->done = std::move(done);
_dialogsProcess->start = std::move(start);
_dialogsProcess->startOne = std::move(startOne);
_dialogsProcess->sliceOne = std::move(sliceOne);
_dialogsProcess->finishOne = std::move(finishOne);
_dialogsProcess->finish = std::move(finish);
requestDialogsSlice();
}
@ -278,33 +313,201 @@ void ApiWrap::requestDialogsSlice() {
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));
auto info = Data::ParseDialogsInfo(result);
if (finished || info.list.empty()) {
finishDialogsList();
} else {
const auto &last = _dialogsProcess->info.list.back();
const auto &last = info.list.back();
_dialogsProcess->offsetId = last.topMessageId;
_dialogsProcess->offsetDate = last.topMessageDate;
_dialogsProcess->offsetPeer = last.input;
appendDialogsSlice(std::move(info));
requestDialogsSlice();
}
}).send();
}
void ApiWrap::appendDialogsSlice(Data::DialogsInfo &&info) {
Expects(_dialogsProcess != nullptr);
Expects(_settings != nullptr);
const auto types = _settings->types | _settings->fullChats;
auto filtered = ranges::view::all(
info.list
) | ranges::view::filter([&](const Data::DialogInfo &info) {
const auto bit = [&] {
using DialogType = Data::DialogInfo::Type;
switch (info.type) {
case DialogType::Personal:
return Settings::Type::PersonalChats;
case DialogType::PrivateGroup:
return Settings::Type::PrivateGroups;
case DialogType::PublicGroup:
return Settings::Type::PublicGroups;
case DialogType::Channel:
return Settings::Type::MyChannels;
}
return Settings::Type(0);
}();
return (types & bit) != 0;
});
auto &list = _dialogsProcess->info.list;
list.reserve(list.size());
for (auto &info : filtered) {
list.push_back(std::move(info));
}
}
void ApiWrap::finishDialogsList() {
Expects(_dialogsProcess != nullptr);
ranges::reverse(_dialogsProcess->info.list);
_dialogsProcess->start(_dialogsProcess->info);
requestNextDialog();
}
void ApiWrap::requestNextDialog() {
Expects(_dialogsProcess != nullptr);
Expects(_dialogsProcess->single == nullptr);
const auto index = ++_dialogsProcess->singleIndex;
if (index < 3) {// _dialogsProcess->info.list.size()) {
const auto &one = _dialogsProcess->info.list[index];
_dialogsProcess->single = std::make_unique<DialogsProcess::Single>(one);
_dialogsProcess->startOne(one);
requestMessagesSlice();
return;
}
finishDialogs();
}
void ApiWrap::requestMessagesSlice() {
Expects(_dialogsProcess != nullptr);
Expects(_dialogsProcess->single != nullptr);
const auto process = _dialogsProcess->single.get();
mainRequest(MTPmessages_GetHistory(
process->peer,
MTP_int(process->offsetId),
MTP_int(0), // offset_date
MTP_int(-kMessagesSliceLimit),
MTP_int(kMessagesSliceLimit),
MTP_int(0), // max_id
MTP_int(0), // min_id
MTP_int(0) // hash
)).done([=](const MTPmessages_Messages &result) mutable {
Expects(_dialogsProcess != nullptr);
Expects(_dialogsProcess->single != nullptr);
const auto process = _dialogsProcess->single.get();
result.visit([&](const MTPDmessages_messagesNotModified &data) {
error("Unexpected messagesNotModified received.");
}, [&](const auto &data) {
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
process->lastSlice = true;
}
loadMessagesFiles(Data::ParseMessagesSlice(
data.vmessages,
data.vusers,
data.vchats));
});
}).send();
}
void ApiWrap::loadMessagesFiles(Data::MessagesSlice &&slice) {
Expects(_dialogsProcess != nullptr);
Expects(_dialogsProcess->single != nullptr);
Expects(!_dialogsProcess->single->slice.has_value());
const auto process = _dialogsProcess->single.get();
if (slice.list.empty()) {
process->lastSlice = true;
}
process->slice = std::move(slice);
process->fileIndex = -1;
loadNextMessageFile();
}
void ApiWrap::loadNextMessageFile() {
Expects(_dialogsProcess != nullptr);
Expects(_dialogsProcess->single != nullptr);
Expects(_dialogsProcess->single->slice.has_value());
const auto process = _dialogsProcess->single.get();
const auto &list = process->slice->list;
++process->fileIndex;
if (process->fileIndex < list.size()) {
loadFile(
list[process->fileIndex].mediaFile,
[=](const QString &path) { loadMessageFileDone(path); });
return;
}
_dialogsProcess->sliceOne(*base::take(process->slice));
if (process->lastSlice) {
finishMessages();
return;
}
Assert(!list.empty());
process->offsetId = list.back().id + 1;
requestMessagesSlice();
}
void ApiWrap::loadMessageFileDone(const QString &relativePath) {
Expects(_dialogsProcess != nullptr);
Expects(_dialogsProcess->single != nullptr);
Expects(_dialogsProcess->single->slice.has_value());
Expects((_dialogsProcess->single->fileIndex >= 0)
&& (_dialogsProcess->single->fileIndex
< _dialogsProcess->single->slice->list.size()));
const auto process = _dialogsProcess->single.get();
const auto index = process->fileIndex;
process->slice->list[index].mediaFile.relativePath = relativePath;
loadNextMessageFile();
}
void ApiWrap::finishMessages() {
Expects(_dialogsProcess != nullptr);
Expects(_dialogsProcess->single != nullptr);
Expects(!_dialogsProcess->single->slice.has_value());
_dialogsProcess->single = nullptr;
_dialogsProcess->finishOne();
requestNextDialog();
}
void ApiWrap::finishDialogs() {
Expects(_dialogsProcess != nullptr);
Expects(_dialogsProcess->single == nullptr);
base::take(_dialogsProcess)->finish();
}
void ApiWrap::loadFile(const Data::File &file, FnMut<void(QString)> done) {
Expects(_fileProcess == nullptr);
Expects(_settings != nullptr);
if (!file.relativePath.isEmpty()) {
done(file.relativePath);
return;
} else if (file.content.isEmpty() && !file.location.dcId) {
done(QString());
return;
}
using namespace Output;
const auto relativePath = File::PrepareRelativePath(
_filesFolder,
_settings->path,
file.suggestedPath);
_fileProcess = std::make_unique<FileProcess>(
_filesFolder + relativePath);
_settings->path + relativePath);
_fileProcess->relativePath = relativePath;
_fileProcess->location = file.location;
_fileProcess->done = std::move(done);
@ -316,8 +519,6 @@ void ApiWrap::loadFile(const Data::File &file, FnMut<void(QString)> done) {
} else {
error(QString("Could not open '%1'.").arg(relativePath));
}
} else if (!file.location.dcId) {
_fileProcess->done(QString());
} else {
loadFilePart();
}

View File

@ -19,16 +19,20 @@ struct UserpicsSlice;
struct ContactsList;
struct SessionsList;
struct DialogsInfo;
struct DialogInfo;
struct MessagesSlice;
} // namespace Data
struct Settings;
class ApiWrap {
public:
ApiWrap(Fn<void(FnMut<void()>)> runner);
void setFilesBaseFolder(const QString &folder);
rpl::producer<RPCError> errors() const;
void startExport(const Settings &settings);
void requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done);
void requestUserpics(
@ -40,7 +44,12 @@ public:
void requestSessions(FnMut<void(Data::SessionsList&&)> done);
void requestDialogs(FnMut<void(Data::DialogsInfo&&)> done);
void requestDialogs(
FnMut<void(const Data::DialogsInfo&)> start,
Fn<void(const Data::DialogInfo&)> startOne,
Fn<void(Data::MessagesSlice&&)> sliceOne,
Fn<void()> finishOne,
FnMut<void()> finish);
~ApiWrap();
@ -52,6 +61,16 @@ private:
void finishUserpics();
void requestDialogsSlice();
void appendDialogsSlice(Data::DialogsInfo &&info);
void finishDialogsList();
void requestNextDialog();
void requestMessagesSlice();
void loadMessagesFiles(Data::MessagesSlice &&slice);
void loadNextMessageFile();
void loadMessageFileDone(const QString &relativePath);
void finishMessages();
void finishDialogs();
void loadFile(const Data::File &file, FnMut<void(QString)> done);
void loadFilePart();
@ -68,7 +87,8 @@ private:
void error(const QString &text);
MTP::ConcurrentSender _mtp;
QString _filesFolder;
std::unique_ptr<Settings> _settings;
MTPInputUser _user = MTP_inputUserSelf();
struct UserpicsProcess;

View File

@ -157,7 +157,7 @@ void Controller::startExport(const Settings &settings) {
return;
}
_writer = Output::CreateWriter(_settings.format);
_api.setFilesBaseFolder(_settings.path);
_api.startExport(_settings);
fillExportSteps();
exportNext();
}
@ -268,8 +268,16 @@ void Controller::exportSessions() {
}
void Controller::exportDialogs() {
_api.requestDialogs([=](Data::DialogsInfo &&result) {
_api.requestDialogs([=](const Data::DialogsInfo &result) {
_writer->writeDialogsStart(result);
}, [=](const Data::DialogInfo &result) {
_writer->writeDialogStart(result);
}, [=](Data::MessagesSlice &&result) {
_writer->writeMessagesSlice(result);
}, [=] {
_writer->writeDialogEnd();
}, [=] {
_writer->writeDialogsEnd();
exportNext();
});
}

View File

@ -53,6 +53,7 @@ struct Settings {
Output::Format format = Output::Format();
Types types = DefaultTypes();
Types fullChats = DefaultFullChats();
MediaSettings defaultMedia;
base::flat_map<Type, MediaSettings> customMedia;
@ -64,6 +65,10 @@ struct Settings {
| Type::PersonalChats;
}
static inline Types DefaultFullChats() {
return Type::PersonalChats;
}
};
} // namespace Export

View File

@ -17,6 +17,10 @@ namespace Output {
File::File(const QString &path) : _path(path) {
}
bool File::empty() const {
return !_offset;
}
File::Result File::writeBlock(const QByteArray &block) {
const auto result = writeBlockAttempt(block);
if (result != Result::Success) {

View File

@ -20,6 +20,8 @@ class File {
public:
File(const QString &path);
bool empty() const;
enum class Result {
Success,
Error,

View File

@ -29,7 +29,7 @@ void SerializeMultiline(
auto offset = 0;
do {
appendTo.append("> ");
appendTo.append(data + offset, newline).append(kLineBreak);
appendTo.append(data + offset, newline - offset).append(kLineBreak);
offset = newline + 1;
newline = value.indexOf('\n', offset);
} while (newline > 0);
@ -89,7 +89,7 @@ bool TextWriter::start(const QString &folder) {
Expects(folder.endsWith('/'));
_folder = folder;
_result = std::make_unique<File>(_folder + "result.txt");
_result = fileWithRelativePath(mainFileRelativePath());
return true;
}
@ -128,7 +128,7 @@ bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
auto lines = QByteArray();
for (const auto &userpic : data.list) {
if (!userpic.date) {
lines.append("(empty photo)");
lines.append("(deleted photo)");
} else {
lines.append(Data::FormatDateTime(userpic.date)).append(" - ");
if (userpic.image.relativePath.isEmpty()) {
@ -153,17 +153,17 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
return true;
}
const auto file = std::make_unique<File>(_folder + "contacts.txt");
const auto file = fileWithRelativePath("contacts.txt");
auto list = std::vector<QByteArray>();
list.reserve(data.list.size());
for (const auto &index : Data::SortedContactsIndices(data)) {
const auto &contact = data.list[index];
if (!contact.id) {
list.push_back("(user unavailable)");
list.push_back("(user unavailable)" + kLineBreak);
} else if (contact.firstName.isEmpty()
&& contact.lastName.isEmpty()
&& contact.phoneNumber.isEmpty()) {
list.push_back("(empty user)" + kLineBreak);
list.push_back("(deleted user)" + kLineBreak);
} else {
list.push_back(SerializeKeyValue({
{ "First name", contact.firstName },
@ -192,7 +192,7 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
return true;
}
const auto file = std::make_unique<File>(_folder + "sessions.txt");
const auto file = fileWithRelativePath("sessions.txt");
auto list = std::vector<QByteArray>();
list.reserve(data.list.size());
for (const auto &session : data.list) {
@ -231,6 +231,8 @@ bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
return true;
}
_dialogsCount = data.list.size();
using Type = Data::DialogInfo::Type;
const auto TypeString = [](Type type) {
switch (type) {
@ -242,20 +244,31 @@ bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
}
Unexpected("Dialog type in TypeString.");
};
const auto NameString = [](
const Data::Utf8String &name,
Type type) -> QByteArray {
if (!name.isEmpty()) {
return name;
}
switch (type) {
case Type::Unknown: return "(unknown)";
case Type::Personal: return "(deleted user)";
case Type::PrivateGroup:
case Type::PublicGroup: return "(deleted group)";
case Type::Channel: return "(deleted 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");
const auto file = fileWithRelativePath("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";
const auto number = Data::NumberToString(++index, digits, '0');
const auto path = "Chats/chat_" + number + ".txt";
list.push_back(SerializeKeyValue({
{ "Name", dialog.name },
{ "Name", NameString(dialog.name, dialog.type) },
{ "Type", TypeString(dialog.type) },
{ "Content", path }
}));
@ -273,14 +286,38 @@ bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
}
bool TextWriter::writeDialogStart(const Data::DialogInfo &data) {
Expects(_dialog == nullptr);
Expects(_dialogIndex < _dialogsCount);
const auto digits = Data::NumberToString(_dialogsCount - 1).size();
const auto number = Data::NumberToString(++_dialogIndex, digits, '0');
_dialog = fileWithRelativePath("Chats/chat_" + number + ".txt");
return true;
}
bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) {
return true;
Expects(_dialog != nullptr);
auto list = std::vector<QByteArray>();
list.reserve(data.list.size());
auto index = 0;
for (const auto &message : data.list) {
list.push_back(SerializeKeyValue({
{ "ID", Data::NumberToString(message.id) },
{ "Date", Data::FormatDateTime(message.date) },
{ "Text", message.text }
}));
}
const auto full = _dialog->empty()
? JoinList(kLineBreak, list)
: kLineBreak + JoinList(kLineBreak, list);
return _dialog->writeBlock(full) == File::Result::Success;
}
bool TextWriter::writeDialogEnd() {
Expects(_dialog != nullptr);
_dialog = nullptr;
return true;
}
@ -293,7 +330,20 @@ bool TextWriter::finish() {
}
QString TextWriter::mainFilePath() {
return _folder + "result.txt";
return pathWithRelativePath(mainFileRelativePath());
}
QString TextWriter::mainFileRelativePath() const {
return "result.txt";
}
QString TextWriter::pathWithRelativePath(const QString &path) const {
return _folder + path;
}
std::unique_ptr<File> TextWriter::fileWithRelativePath(
const QString &path) const {
return std::make_unique<File>(_folder + path);
}
} // namespace Output

View File

@ -38,11 +38,19 @@ public:
QString mainFilePath() override;
private:
QString mainFileRelativePath() const;
QString pathWithRelativePath(const QString &path) const;
std::unique_ptr<File> fileWithRelativePath(const QString &path) const;
QString _folder;
std::unique_ptr<File> _result;
int _userpicsCount = 0;
int _dialogsCount = 0;
int _dialogIndex = 0;
std::unique_ptr<File> _dialog;
};
} // namespace Output