mirror of
https://github.com/vale981/tdesktop
synced 2025-03-06 02:01:40 -05:00
Export chat messages text.
This commit is contained in:
parent
35ffc03988
commit
2b36dd660b
10 changed files with 418 additions and 72 deletions
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -20,6 +20,8 @@ class File {
|
|||
public:
|
||||
File(const QString &path);
|
||||
|
||||
bool empty() const;
|
||||
|
||||
enum class Result {
|
||||
Success,
|
||||
Error,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue