mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-25 04:38:23 +00:00
Display errors in export UI.
All errors are now fatal errors :(
This commit is contained in:
parent
7d4e23448e
commit
5f01751660
@ -1654,7 +1654,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_passport_bad_name" = "Please use latin characters only.";
|
||||
|
||||
"lng_export_title" = "Personal data export";
|
||||
"lng_export_option_info" = "Personal info";
|
||||
"lng_export_option_info" = "Personal information";
|
||||
"lng_export_option_contacts" = "Contacts list";
|
||||
"lng_export_option_sessions" = "Sessions list";
|
||||
"lng_export_header_chats" = "Chats export settings";
|
||||
@ -1675,6 +1675,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
"lng_export_option_files" = "Files";
|
||||
"lng_export_option_size_limit" = "Size limit: {size}";
|
||||
"lng_export_start" = "Export";
|
||||
"lng_export_state_initializing" = "Initializing...";
|
||||
"lng_export_state_userpics" = "Personal photos";
|
||||
"lng_export_state_chats_list" = "Processing chats...";
|
||||
"lng_export_state_chats" = "Chats";
|
||||
"lng_export_state_progress" = "{count} / {total}";
|
||||
"lng_export_state_photo" = "Photo";
|
||||
"lng_export_state_video_file" = "Video file";
|
||||
"lng_export_state_voice_message" = "Voice message";
|
||||
"lng_export_state_video_message" = "Round video message";
|
||||
"lng_export_state_sticker" = "Sticker";
|
||||
"lng_export_state_gif" = "Animated GIF";
|
||||
|
||||
// Wnd specific
|
||||
|
||||
|
@ -250,6 +250,21 @@ QString CleanDocumentName(QString name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
QString DocumentFolder(const Document &data) {
|
||||
if (data.isVideoFile) {
|
||||
return "VideoFiles";
|
||||
} else if (data.isAnimated) {
|
||||
return "AnimatedGIFs";
|
||||
} else if (data.isSticker) {
|
||||
return "Stickers";
|
||||
} else if (data.isVoiceMessage) {
|
||||
return "VoiceMessages";
|
||||
} else if (data.isVideoMessage) {
|
||||
return "RoundVideoMessages";
|
||||
}
|
||||
return "Files";
|
||||
}
|
||||
|
||||
Document ParseDocument(
|
||||
const MTPDocument &data,
|
||||
const QString &suggestedFolder,
|
||||
@ -267,6 +282,7 @@ Document ParseDocument(
|
||||
result.mime = ParseString(data.vmime_type);
|
||||
ParseAttributes(result, data.vattributes);
|
||||
result.file.suggestedPath = suggestedFolder
|
||||
+ DocumentFolder(result) + '/'
|
||||
+ CleanDocumentName(
|
||||
ComputeDocumentName(result, date ? date : result.date));
|
||||
}, [&](const MTPDdocumentEmpty &data) {
|
||||
@ -548,10 +564,7 @@ Media ParseMedia(
|
||||
result.content = UnsupportedMedia();
|
||||
}, [&](const MTPDmessageMediaDocument &data) {
|
||||
result.content = data.has_document()
|
||||
? ParseDocument(
|
||||
data.vdocument,
|
||||
folder + "Files/",
|
||||
date)
|
||||
? ParseDocument(data.vdocument, folder, date)
|
||||
: Document();
|
||||
if (data.has_ttl_seconds()) {
|
||||
result.ttl = data.vttl_seconds.v;
|
||||
|
@ -427,6 +427,9 @@ struct DialogInfo {
|
||||
bool onlyMyMessages = false;
|
||||
QString relativePath;
|
||||
|
||||
// Filled when requesting dialog messages.
|
||||
int messagesCount = 0;
|
||||
|
||||
};
|
||||
|
||||
struct DialogsInfo {
|
||||
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "export/export_settings.h"
|
||||
#include "export/data/export_data_types.h"
|
||||
#include "export/output/export_output_result.h"
|
||||
#include "export/output/export_output_file.h"
|
||||
#include "mtproto/rpc_sender.h"
|
||||
#include "base/value_ordering.h"
|
||||
@ -114,11 +115,13 @@ struct ApiWrap::StartProcess {
|
||||
};
|
||||
|
||||
struct ApiWrap::UserpicsProcess {
|
||||
FnMut<void(Data::UserpicsInfo&&)> start;
|
||||
Fn<void(Data::UserpicsSlice&&)> handleSlice;
|
||||
FnMut<bool(Data::UserpicsInfo&&)> start;
|
||||
Fn<bool(DownloadProgress)> fileProgress;
|
||||
Fn<bool(Data::UserpicsSlice&&)> handleSlice;
|
||||
FnMut<void()> finish;
|
||||
|
||||
base::optional<Data::UserpicsSlice> slice;
|
||||
uint64 maxId = 0;
|
||||
bool lastSlice = false;
|
||||
int fileIndex = -1;
|
||||
|
||||
@ -130,6 +133,7 @@ struct ApiWrap::FileProcess {
|
||||
Output::File file;
|
||||
QString relativePath;
|
||||
|
||||
Fn<bool(FileProgress)> progress;
|
||||
FnMut<void(const QString &relativePath)> done;
|
||||
|
||||
Data::FileLocation location;
|
||||
@ -144,18 +148,25 @@ struct ApiWrap::FileProcess {
|
||||
|
||||
};
|
||||
|
||||
struct ApiWrap::FileProgress {
|
||||
int ready = 0;
|
||||
int total = 0;
|
||||
};
|
||||
|
||||
struct ApiWrap::LeftChannelsProcess {
|
||||
Fn<bool(int count)> progress;
|
||||
FnMut<void(Data::DialogsInfo&&)> done;
|
||||
|
||||
Data::DialogsInfo info;
|
||||
|
||||
rpl::variable<int> count;
|
||||
int fullCount = 0;
|
||||
int offset = 0;
|
||||
bool finished = false;
|
||||
|
||||
};
|
||||
|
||||
struct ApiWrap::DialogsProcess {
|
||||
Fn<bool(int count)> progress;
|
||||
FnMut<void(Data::DialogsInfo&&)> done;
|
||||
|
||||
Data::DialogsInfo info;
|
||||
@ -164,14 +175,14 @@ struct ApiWrap::DialogsProcess {
|
||||
int32 offsetId = 0;
|
||||
MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
|
||||
|
||||
rpl::variable<int> count;
|
||||
|
||||
};
|
||||
|
||||
struct ApiWrap::ChatProcess {
|
||||
Data::DialogInfo info;
|
||||
|
||||
Fn<void(Data::MessagesSlice&&)> handleSlice;
|
||||
FnMut<bool(const Data::DialogInfo &)> start;
|
||||
Fn<bool(DownloadProgress)> fileProgress;
|
||||
Fn<bool(Data::MessagesSlice&&)> handleSlice;
|
||||
FnMut<void()> done;
|
||||
|
||||
int32 offsetId = 1;
|
||||
@ -247,6 +258,10 @@ rpl::producer<RPCError> ApiWrap::errors() const {
|
||||
return _errors.events();
|
||||
}
|
||||
|
||||
rpl::producer<Output::Result> ApiWrap::ioErrors() const {
|
||||
return _ioErrors.events();
|
||||
}
|
||||
|
||||
void ApiWrap::startExport(
|
||||
const Settings &settings,
|
||||
FnMut<void(StartInfo)> done) {
|
||||
@ -260,9 +275,11 @@ void ApiWrap::startExport(
|
||||
using Step = StartProcess::Step;
|
||||
if (_settings->types & Settings::Type::Userpics) {
|
||||
_startProcess->steps.push_back(Step::UserpicsCount);
|
||||
} else if (_settings->types & Settings::Type::AnyChatsMask) {
|
||||
}
|
||||
if (_settings->types & Settings::Type::AnyChatsMask) {
|
||||
_startProcess->steps.push_back(Step::DialogsCount);
|
||||
} else if (_settings->types & Settings::Type::GroupsChannelsMask) {
|
||||
}
|
||||
if (_settings->types & Settings::Type::GroupsChannelsMask) {
|
||||
_startProcess->steps.push_back(Step::LeftChannelsCount);
|
||||
}
|
||||
startMainSession([=] {
|
||||
@ -361,9 +378,11 @@ void ApiWrap::finishStartProcess() {
|
||||
}
|
||||
|
||||
void ApiWrap::requestLeftChannelsList(
|
||||
Fn<bool(int count)> progress,
|
||||
FnMut<void(Data::DialogsInfo&&)> done) {
|
||||
Expects(_leftChannelsProcess != nullptr);
|
||||
|
||||
_leftChannelsProcess->progress = std::move(progress);
|
||||
_leftChannelsProcess->done = std::move(done);
|
||||
requestLeftChannelsSlice();
|
||||
}
|
||||
@ -382,27 +401,18 @@ void ApiWrap::requestLeftChannelsSlice() {
|
||||
});
|
||||
}
|
||||
|
||||
rpl::producer<int> ApiWrap::leftChannelsLoadedCount() const {
|
||||
Expects(_leftChannelsProcess != nullptr);
|
||||
|
||||
return _leftChannelsProcess->count.value();
|
||||
}
|
||||
|
||||
void ApiWrap::requestDialogsList(FnMut<void(Data::DialogsInfo&&)> done) {
|
||||
void ApiWrap::requestDialogsList(
|
||||
Fn<bool(int count)> progress,
|
||||
FnMut<void(Data::DialogsInfo&&)> done) {
|
||||
Expects(_dialogsProcess == nullptr);
|
||||
|
||||
_dialogsProcess = std::make_unique<DialogsProcess>();
|
||||
_dialogsProcess->progress = std::move(progress);
|
||||
_dialogsProcess->done = std::move(done);
|
||||
|
||||
requestDialogsSlice();
|
||||
}
|
||||
|
||||
rpl::producer<int> ApiWrap::dialogsLoadedCount() const {
|
||||
Expects(_dialogsProcess != nullptr);
|
||||
|
||||
return _dialogsProcess->count.value();
|
||||
}
|
||||
|
||||
void ApiWrap::startMainSession(FnMut<void()> done) {
|
||||
const auto sizeLimit = _settings->media.sizeLimit;
|
||||
const auto hasFiles = (_settings->media.types != 0) && (sizeLimit > 0);
|
||||
@ -456,33 +466,35 @@ void ApiWrap::requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done) {
|
||||
}
|
||||
|
||||
void ApiWrap::requestUserpics(
|
||||
FnMut<void(Data::UserpicsInfo&&)> start,
|
||||
Fn<void(Data::UserpicsSlice&&)> slice,
|
||||
FnMut<bool(Data::UserpicsInfo&&)> start,
|
||||
Fn<bool(DownloadProgress)> progress,
|
||||
Fn<bool(Data::UserpicsSlice&&)> slice,
|
||||
FnMut<void()> finish) {
|
||||
Expects(_userpicsProcess == nullptr);
|
||||
|
||||
_userpicsProcess = std::make_unique<UserpicsProcess>();
|
||||
_userpicsProcess->start = std::move(start);
|
||||
_userpicsProcess->fileProgress = std::move(progress);
|
||||
_userpicsProcess->handleSlice = std::move(slice);
|
||||
_userpicsProcess->finish = std::move(finish);
|
||||
|
||||
mainRequest(MTPphotos_GetUserPhotos(
|
||||
_user,
|
||||
MTP_int(0), // offset
|
||||
MTP_long(0), // max_id
|
||||
MTP_int(0), // offset
|
||||
MTP_long(_userpicsProcess->maxId),
|
||||
MTP_int(kUserpicsSliceLimit)
|
||||
)).done([=](const MTPphotos_Photos &result) mutable {
|
||||
Expects(_userpicsProcess != nullptr);
|
||||
|
||||
_userpicsProcess->start([&] {
|
||||
auto info = Data::UserpicsInfo();
|
||||
result.match([&](const MTPDphotos_photos &data) {
|
||||
info.count = data.vphotos.v.size();
|
||||
}, [&](const MTPDphotos_photosSlice &data) {
|
||||
info.count = data.vcount.v;
|
||||
});
|
||||
return info;
|
||||
}());
|
||||
auto startInfo = result.match(
|
||||
[](const MTPDphotos_photos &data) {
|
||||
return Data::UserpicsInfo{ data.vphotos.v.size() };
|
||||
}, [](const MTPDphotos_photosSlice &data) {
|
||||
return Data::UserpicsInfo{ data.vcount.v };
|
||||
});
|
||||
if (!_userpicsProcess->start(std::move(startInfo))) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleUserpicsSlice(result);
|
||||
}).send();
|
||||
@ -523,34 +535,54 @@ void ApiWrap::loadNextUserpic() {
|
||||
}
|
||||
const auto ready = processFileLoad(
|
||||
list[index].image.file,
|
||||
[=](FileProgress value) { return loadUserpicProgress(value); },
|
||||
[=](const QString &path) { loadUserpicDone(path); });
|
||||
if (!ready) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const auto lastUserpicId = list.empty()
|
||||
? base::none
|
||||
: base::make_optional(list.back().id);
|
||||
finishUserpicsSlice();
|
||||
}
|
||||
|
||||
if (!list.empty()) {
|
||||
_userpicsProcess->handleSlice(*base::take(_userpicsProcess->slice));
|
||||
void ApiWrap::finishUserpicsSlice() {
|
||||
Expects(_userpicsProcess != nullptr);
|
||||
Expects(_userpicsProcess->slice.has_value());
|
||||
|
||||
auto slice = *base::take(_userpicsProcess->slice);
|
||||
if (!slice.list.empty()) {
|
||||
_userpicsProcess->maxId = slice.list.back().id;
|
||||
if (!_userpicsProcess->handleSlice(std::move(slice))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_userpicsProcess->lastSlice) {
|
||||
finishUserpics();
|
||||
return;
|
||||
}
|
||||
|
||||
Assert(lastUserpicId.has_value());
|
||||
mainRequest(MTPphotos_GetUserPhotos(
|
||||
_user,
|
||||
MTP_int(0),
|
||||
MTP_long(*lastUserpicId),
|
||||
MTP_int(0), // offset
|
||||
MTP_long(_userpicsProcess->maxId),
|
||||
MTP_int(kUserpicsSliceLimit)
|
||||
)).done([=](const MTPphotos_Photos &result) {
|
||||
handleUserpicsSlice(result);
|
||||
}).send();
|
||||
}
|
||||
|
||||
bool ApiWrap::loadUserpicProgress(FileProgress progress) {
|
||||
Expects(_userpicsProcess != nullptr);
|
||||
Expects(_userpicsProcess->slice.has_value());
|
||||
Expects((_userpicsProcess->fileIndex >= 0)
|
||||
&& (_userpicsProcess->fileIndex
|
||||
< _userpicsProcess->slice->list.size()));
|
||||
|
||||
return _userpicsProcess->fileProgress(DownloadProgress{
|
||||
_userpicsProcess->fileIndex,
|
||||
progress.ready,
|
||||
progress.total });
|
||||
}
|
||||
|
||||
void ApiWrap::loadUserpicDone(const QString &relativePath) {
|
||||
Expects(_userpicsProcess != nullptr);
|
||||
Expects(_userpicsProcess->slice.has_value());
|
||||
@ -588,16 +620,25 @@ void ApiWrap::requestSessions(FnMut<void(Data::SessionsList&&)> done) {
|
||||
|
||||
void ApiWrap::requestMessages(
|
||||
const Data::DialogInfo &info,
|
||||
Fn<void(Data::MessagesSlice&&)> slice,
|
||||
FnMut<bool(const Data::DialogInfo &)> start,
|
||||
Fn<bool(DownloadProgress)> progress,
|
||||
Fn<bool(Data::MessagesSlice&&)> slice,
|
||||
FnMut<void()> done) {
|
||||
Expects(_chatProcess == nullptr);
|
||||
|
||||
_chatProcess = std::make_unique<ChatProcess>();
|
||||
_chatProcess->info = info;
|
||||
_chatProcess->start = std::move(start);
|
||||
_chatProcess->fileProgress = std::move(progress);
|
||||
_chatProcess->handleSlice = std::move(slice);
|
||||
_chatProcess->done = std::move(done);
|
||||
|
||||
requestMessagesSlice();
|
||||
requestMessagesSlice([=](int count) {
|
||||
Expects(_chatProcess != nullptr);
|
||||
|
||||
_chatProcess->info.messagesCount = count;
|
||||
return _chatProcess->start(_chatProcess->info);
|
||||
});
|
||||
}
|
||||
|
||||
void ApiWrap::requestDialogsSlice() {
|
||||
@ -628,6 +669,11 @@ void ApiWrap::requestDialogsSlice() {
|
||||
|
||||
appendDialogsSlice(std::move(info));
|
||||
|
||||
const auto count = _dialogsProcess->info.list.size();
|
||||
if (!_dialogsProcess->progress(count)) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestDialogsSlice();
|
||||
}
|
||||
}).send();
|
||||
@ -654,7 +700,7 @@ void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
|
||||
Expects(_leftChannelsProcess != nullptr);
|
||||
|
||||
mainRequest(MTPchannels_GetLeftChannels(
|
||||
MTP_int(_leftChannelsProcess->info.list.size())
|
||||
MTP_int(_leftChannelsProcess->offset)
|
||||
)).done([=, done = std::move(done)](
|
||||
const MTPmessages_Chats &result) mutable {
|
||||
Expects(_leftChannelsProcess != nullptr);
|
||||
@ -662,6 +708,11 @@ void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
|
||||
appendLeftChannelsSlice(Data::ParseLeftChannelsInfo(result));
|
||||
|
||||
const auto process = _leftChannelsProcess.get();
|
||||
process->offset += result.match(
|
||||
[](const auto &data) {
|
||||
return int(data.vchats.v.size());
|
||||
});
|
||||
|
||||
process->fullCount = result.match(
|
||||
[](const MTPDmessages_chats &data) {
|
||||
return int(data.vchats.v.size());
|
||||
@ -676,7 +727,11 @@ void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
|
||||
return data.vchats.v.isEmpty();
|
||||
});
|
||||
|
||||
process->count = process->info.list.size();
|
||||
if (process->progress) {
|
||||
if (!process->progress(process->info.list.size())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
done();
|
||||
}).send();
|
||||
@ -710,23 +765,30 @@ void ApiWrap::appendChatsSlice(
|
||||
}
|
||||
}
|
||||
|
||||
void ApiWrap::requestMessagesSlice() {
|
||||
void ApiWrap::requestMessagesSlice(FnMut<bool(int count)> start) {
|
||||
Expects(_chatProcess != nullptr);
|
||||
|
||||
// #TODO export
|
||||
if (_chatProcess->info.input.match([](const MTPDinputPeerUser &value) {
|
||||
return !value.vaccess_hash.v;
|
||||
}, [](const auto &data) { return false; })) {
|
||||
finishMessages();
|
||||
return;
|
||||
}
|
||||
|
||||
auto handleResult = [=](const MTPmessages_Messages &result) mutable {
|
||||
auto handleResult = [=, start = std::move(start)](
|
||||
const MTPmessages_Messages &result) mutable {
|
||||
Expects(_chatProcess != nullptr);
|
||||
|
||||
const auto count = result.match(
|
||||
[](const MTPDmessages_messages &data) {
|
||||
return data.vmessages.v.size();
|
||||
}, [](const MTPDmessages_messagesSlice &data) {
|
||||
return data.vcount.v;
|
||||
}, [](const MTPDmessages_channelMessages &data) {
|
||||
return data.vcount.v;
|
||||
}, [](const MTPDmessages_messagesNotModified &data) {
|
||||
return 0;
|
||||
});
|
||||
|
||||
result.match([&](const MTPDmessages_messagesNotModified &data) {
|
||||
error("Unexpected messagesNotModified received.");
|
||||
}, [&](const auto &data) {
|
||||
if (start && !start(count)) {
|
||||
return;
|
||||
}
|
||||
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
|
||||
_chatProcess->lastSlice = true;
|
||||
}
|
||||
@ -790,8 +852,12 @@ void ApiWrap::loadNextMessageFile() {
|
||||
if (index >= list.size()) {
|
||||
break;
|
||||
}
|
||||
const auto fileProgress = [=](FileProgress value) {
|
||||
return loadMessageFileProgress(value);
|
||||
};
|
||||
const auto ready = processFileLoad(
|
||||
list[index].file(),
|
||||
fileProgress,
|
||||
[=](const QString &path) { loadMessageFileDone(path); },
|
||||
&list[index]);
|
||||
if (!ready) {
|
||||
@ -808,7 +874,9 @@ void ApiWrap::finishMessagesSlice() {
|
||||
auto slice = *base::take(_chatProcess->slice);
|
||||
if (!slice.list.empty()) {
|
||||
_chatProcess->offsetId = slice.list.back().id + 1;
|
||||
_chatProcess->handleSlice(std::move(slice));
|
||||
if (!_chatProcess->handleSlice(std::move(slice))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_chatProcess->lastSlice) {
|
||||
finishMessages();
|
||||
@ -817,6 +885,18 @@ void ApiWrap::finishMessagesSlice() {
|
||||
}
|
||||
}
|
||||
|
||||
bool ApiWrap::loadMessageFileProgress(FileProgress progress) {
|
||||
Expects(_chatProcess != nullptr);
|
||||
Expects(_chatProcess->slice.has_value());
|
||||
Expects((_chatProcess->fileIndex >= 0)
|
||||
&& (_chatProcess->fileIndex < _chatProcess->slice->list.size()));
|
||||
|
||||
return _chatProcess->fileProgress(DownloadProgress{
|
||||
_chatProcess->fileIndex,
|
||||
progress.ready,
|
||||
progress.total });
|
||||
}
|
||||
|
||||
void ApiWrap::loadMessageFileDone(const QString &relativePath) {
|
||||
Expects(_chatProcess != nullptr);
|
||||
Expects(_chatProcess->slice.has_value());
|
||||
@ -838,6 +918,7 @@ void ApiWrap::finishMessages() {
|
||||
|
||||
bool ApiWrap::processFileLoad(
|
||||
Data::File &file,
|
||||
Fn<bool(FileProgress)> progress,
|
||||
FnMut<void(QString)> done,
|
||||
Data::Message *message) {
|
||||
using SkipReason = Data::File::SkipReason;
|
||||
@ -848,7 +929,7 @@ bool ApiWrap::processFileLoad(
|
||||
file.skipReason = SkipReason::Unavailable;
|
||||
return true;
|
||||
} else if (writePreloadedFile(file)) {
|
||||
return true;
|
||||
return !file.relativePath.isEmpty();
|
||||
}
|
||||
|
||||
using Type = MediaSettings::Type;
|
||||
@ -878,7 +959,7 @@ bool ApiWrap::processFileLoad(
|
||||
file.skipReason = SkipReason::FileSize;
|
||||
return true;
|
||||
}
|
||||
loadFile(file, std::move(done));
|
||||
loadFile(file, std::move(progress), std::move(done));
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -893,25 +974,39 @@ bool ApiWrap::writePreloadedFile(Data::File &file) {
|
||||
} else if (!file.content.isEmpty()) {
|
||||
const auto process = prepareFileProcess(file);
|
||||
auto &output = _fileProcess->file;
|
||||
if (output.writeBlock(file.content) == File::Result::Success) {
|
||||
if (const auto result = output.writeBlock(file.content)) {
|
||||
file.relativePath = process->relativePath;
|
||||
_fileCache->save(file.location, file.relativePath);
|
||||
return true;
|
||||
} else {
|
||||
ioError(result);
|
||||
}
|
||||
error(QString("Could not write '%1'.").arg(process->relativePath));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ApiWrap::loadFile(
|
||||
const Data::File &file,
|
||||
Fn<bool(FileProgress)> progress,
|
||||
FnMut<void(QString)> done) {
|
||||
Expects(_fileProcess == nullptr);
|
||||
Expects(file.location.dcId != 0);
|
||||
|
||||
_fileProcess = prepareFileProcess(file);
|
||||
_fileProcess->progress = std::move(progress);
|
||||
_fileProcess->done = std::move(done);
|
||||
|
||||
|
||||
if (_fileProcess->progress) {
|
||||
const auto progress = FileProgress{
|
||||
_fileProcess->file.size(),
|
||||
_fileProcess->size
|
||||
};
|
||||
if (!_fileProcess->progress(progress)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
loadFilePart();
|
||||
}
|
||||
|
||||
@ -989,14 +1084,19 @@ void ApiWrap::filePartDone(int offset, const MTPupload_File &result) {
|
||||
auto &file = _fileProcess->file;
|
||||
while (!requests.empty() && !requests.front().bytes.isEmpty()) {
|
||||
const auto &bytes = requests.front().bytes;
|
||||
if (file.writeBlock(bytes) != Output::File::Result::Success) {
|
||||
error(QString("Could not write bytes to '%1'."
|
||||
).arg(_fileProcess->relativePath));
|
||||
if (const auto result = file.writeBlock(bytes); !result) {
|
||||
ioError(result);
|
||||
return;
|
||||
}
|
||||
requests.pop_front();
|
||||
}
|
||||
|
||||
if (_fileProcess->progress) {
|
||||
_fileProcess->progress(FileProgress{
|
||||
file.size(),
|
||||
_fileProcess->size });
|
||||
}
|
||||
|
||||
if (!requests.empty()
|
||||
|| !_fileProcess->size
|
||||
|| _fileProcess->size > _fileProcess->offset) {
|
||||
@ -1019,6 +1119,10 @@ void ApiWrap::error(const QString &text) {
|
||||
error(MTP_rpc_error(MTP_int(0), MTP_string("API_ERROR: " + text)));
|
||||
}
|
||||
|
||||
void ApiWrap::ioError(const Output::Result &result) {
|
||||
_ioErrors.fire_copy(result);
|
||||
}
|
||||
|
||||
ApiWrap::~ApiWrap() = default;
|
||||
|
||||
} // namespace Export
|
||||
|
@ -25,6 +25,10 @@ struct MessagesSlice;
|
||||
struct Message;
|
||||
} // namespace Data
|
||||
|
||||
namespace Output {
|
||||
struct Result;
|
||||
} // namespace Output
|
||||
|
||||
struct Settings;
|
||||
|
||||
class ApiWrap {
|
||||
@ -32,6 +36,7 @@ public:
|
||||
ApiWrap(Fn<void(FnMut<void()>)> runner);
|
||||
|
||||
rpl::producer<RPCError> errors() const;
|
||||
rpl::producer<Output::Result> ioErrors() const;
|
||||
|
||||
struct StartInfo {
|
||||
int userpicsCount = 0;
|
||||
@ -42,17 +47,24 @@ public:
|
||||
const Settings &settings,
|
||||
FnMut<void(StartInfo)> done);
|
||||
|
||||
void requestLeftChannelsList(FnMut<void(Data::DialogsInfo&&)> done);
|
||||
rpl::producer<int> leftChannelsLoadedCount() const;
|
||||
|
||||
void requestDialogsList(FnMut<void(Data::DialogsInfo&&)> done);
|
||||
rpl::producer<int> dialogsLoadedCount() const;
|
||||
void requestLeftChannelsList(
|
||||
Fn<bool(int count)> progress,
|
||||
FnMut<void(Data::DialogsInfo&&)> done);
|
||||
void requestDialogsList(
|
||||
Fn<bool(int count)> progress,
|
||||
FnMut<void(Data::DialogsInfo&&)> done);
|
||||
|
||||
void requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done);
|
||||
|
||||
struct DownloadProgress {
|
||||
int itemIndex = 0;
|
||||
int ready = 0;
|
||||
int total = 0;
|
||||
};
|
||||
void requestUserpics(
|
||||
FnMut<void(Data::UserpicsInfo&&)> start,
|
||||
Fn<void(Data::UserpicsSlice&&)> slice,
|
||||
FnMut<bool(Data::UserpicsInfo&&)> start,
|
||||
Fn<bool(DownloadProgress)> progress,
|
||||
Fn<bool(Data::UserpicsSlice&&)> slice,
|
||||
FnMut<void()> finish);
|
||||
|
||||
void requestContacts(FnMut<void(Data::ContactsList&&)> done);
|
||||
@ -61,7 +73,9 @@ public:
|
||||
|
||||
void requestMessages(
|
||||
const Data::DialogInfo &info,
|
||||
Fn<void(Data::MessagesSlice&&)> slice,
|
||||
FnMut<bool(const Data::DialogInfo &)> start,
|
||||
Fn<bool(DownloadProgress)> progress,
|
||||
Fn<bool(Data::MessagesSlice&&)> slice,
|
||||
FnMut<void()> done);
|
||||
|
||||
~ApiWrap();
|
||||
@ -71,6 +85,7 @@ private:
|
||||
struct StartProcess;
|
||||
struct UserpicsProcess;
|
||||
struct FileProcess;
|
||||
struct FileProgress;
|
||||
struct LeftChannelsProcess;
|
||||
struct DialogsProcess;
|
||||
struct ChatProcess;
|
||||
@ -85,7 +100,9 @@ private:
|
||||
void handleUserpicsSlice(const MTPphotos_Photos &result);
|
||||
void loadUserpicsFiles(Data::UserpicsSlice &&slice);
|
||||
void loadNextUserpic();
|
||||
bool loadUserpicProgress(FileProgress value);
|
||||
void loadUserpicDone(const QString &relativePath);
|
||||
void finishUserpicsSlice();
|
||||
void finishUserpics();
|
||||
|
||||
void requestDialogsSlice();
|
||||
@ -98,15 +115,17 @@ private:
|
||||
|
||||
void appendChatsSlice(Data::DialogsInfo &to, Data::DialogsInfo &&info);
|
||||
|
||||
void requestMessagesSlice();
|
||||
void requestMessagesSlice(FnMut<bool(int count)> start = nullptr);
|
||||
void loadMessagesFiles(Data::MessagesSlice &&slice);
|
||||
void loadNextMessageFile();
|
||||
bool loadMessageFileProgress(FileProgress value);
|
||||
void loadMessageFileDone(const QString &relativePath);
|
||||
void finishMessagesSlice();
|
||||
void finishMessages();
|
||||
|
||||
bool processFileLoad(
|
||||
Data::File &file,
|
||||
Fn<bool(FileProgress)> progress,
|
||||
FnMut<void(QString)> done,
|
||||
Data::Message *message = nullptr);
|
||||
std::unique_ptr<FileProcess> prepareFileProcess(
|
||||
@ -114,6 +133,7 @@ private:
|
||||
bool writePreloadedFile(Data::File &file);
|
||||
void loadFile(
|
||||
const Data::File &file,
|
||||
Fn<bool(FileProgress)> progress,
|
||||
FnMut<void(QString)> done);
|
||||
void loadFilePart();
|
||||
void filePartDone(int offset, const MTPupload_File &result);
|
||||
@ -127,6 +147,7 @@ private:
|
||||
|
||||
void error(RPCError &&error);
|
||||
void error(const QString &text);
|
||||
void ioError(const Output::Result &result);
|
||||
|
||||
MTP::ConcurrentSender _mtp;
|
||||
base::optional<uint64> _takeoutId;
|
||||
@ -143,6 +164,7 @@ private:
|
||||
std::unique_ptr<ChatProcess> _chatProcess;
|
||||
|
||||
rpl::event_stream<RPCError> _errors;
|
||||
rpl::event_stream<Output::Result> _ioErrors;
|
||||
|
||||
};
|
||||
|
||||
|
@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "export/export_settings.h"
|
||||
#include "export/data/export_data_types.h"
|
||||
#include "export/output/export_output_abstract.h"
|
||||
#include "export/output/export_output_result.h"
|
||||
|
||||
namespace Export {
|
||||
|
||||
@ -34,9 +35,11 @@ public:
|
||||
|
||||
private:
|
||||
using Step = ProcessingState::Step;
|
||||
using DownloadProgress = ApiWrap::DownloadProgress;
|
||||
|
||||
void setState(State &&state);
|
||||
void ioError(const QString &path);
|
||||
bool ioCatchError(Output::Result result);
|
||||
void setFinishedState();
|
||||
|
||||
//void requestPasswordState();
|
||||
@ -65,11 +68,11 @@ private:
|
||||
ProcessingState stateLeftChannelsList(int processed) const;
|
||||
ProcessingState stateDialogsList(int processed) const;
|
||||
ProcessingState statePersonalInfo() const;
|
||||
ProcessingState stateUserpics() const;
|
||||
ProcessingState stateUserpics(DownloadProgress progress) const;
|
||||
ProcessingState stateContacts() const;
|
||||
ProcessingState stateSessions() const;
|
||||
ProcessingState stateLeftChannels() const;
|
||||
ProcessingState stateDialogs() const;
|
||||
ProcessingState stateLeftChannels(DownloadProgress progress) const;
|
||||
ProcessingState stateDialogs(DownloadProgress progress) const;
|
||||
|
||||
int substepsInStep(Step step) const;
|
||||
|
||||
@ -77,15 +80,23 @@ private:
|
||||
|
||||
ApiWrap _api;
|
||||
Settings _settings;
|
||||
|
||||
Data::DialogsInfo _leftChannelsInfo;
|
||||
Data::DialogsInfo _dialogsInfo;
|
||||
int _leftChannelIndex = -1;
|
||||
|
||||
Data::DialogsInfo _dialogsInfo;
|
||||
int _dialogIndex = -1;
|
||||
|
||||
int _messagesWritten = 0;
|
||||
int _messagesCount = 0;
|
||||
|
||||
int _userpicsWritten = 0;
|
||||
int _userpicsCount = 0;
|
||||
|
||||
// rpl::variable<State> fails to compile in MSVC :(
|
||||
State _state;
|
||||
rpl::event_stream<State> _stateChanges;
|
||||
std::vector<int> _substepsInStep;
|
||||
std::shared_ptr<const std::vector<int>> _substepsInStep;
|
||||
|
||||
std::unique_ptr<Output::AbstractWriter> _writer;
|
||||
std::vector<Step> _steps;
|
||||
@ -100,7 +111,12 @@ Controller::Controller(crl::weak_on_queue<Controller> weak)
|
||||
, _state(PasswordCheckState{}) {
|
||||
_api.errors(
|
||||
) | rpl::start_with_next([=](RPCError &&error) {
|
||||
setState(ErrorState{ ErrorState::Type::API, std::move(error) });
|
||||
setState(ApiErrorState{ std::move(error) });
|
||||
}, _lifetime);
|
||||
|
||||
_api.ioErrors(
|
||||
) | rpl::start_with_next([=](const Output::Result &result) {
|
||||
ioCatchError(result);
|
||||
}, _lifetime);
|
||||
|
||||
//requestPasswordState();
|
||||
@ -127,7 +143,15 @@ void Controller::setState(State &&state) {
|
||||
}
|
||||
|
||||
void Controller::ioError(const QString &path) {
|
||||
setState(ErrorState{ ErrorState::Type::IO, base::none, path });
|
||||
setState(OutputErrorState{ path });
|
||||
}
|
||||
|
||||
bool Controller::ioCatchError(Output::Result result) {
|
||||
if (!result) {
|
||||
ioError(result.path);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//void Controller::submitPassword(const QString &password) {
|
||||
@ -223,7 +247,7 @@ void Controller::fillExportSteps() {
|
||||
using Type = Settings::Type;
|
||||
_steps.push_back(Step::Initializing);
|
||||
if (_settings.types & Type::GroupsChannelsMask) {
|
||||
_steps.push_back(Step::LeftChannels);
|
||||
_steps.push_back(Step::LeftChannelsList);
|
||||
}
|
||||
if (_settings.types & Type::AnyChatsMask) {
|
||||
_steps.push_back(Step::DialogsList);
|
||||
@ -243,15 +267,19 @@ void Controller::fillExportSteps() {
|
||||
if (_settings.types & Type::AnyChatsMask) {
|
||||
_steps.push_back(Step::Dialogs);
|
||||
}
|
||||
if (_settings.types & Type::GroupsChannelsMask) {
|
||||
_steps.push_back(Step::LeftChannels);
|
||||
}
|
||||
}
|
||||
|
||||
void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
|
||||
auto result = std::vector<int>();
|
||||
const auto push = [&](Step step, int count) {
|
||||
const auto index = static_cast<int>(step);
|
||||
if (index >= _substepsInStep.size()) {
|
||||
_substepsInStep.resize(index + 1, 0);
|
||||
if (index >= result.size()) {
|
||||
result.resize(index + 1, 0);
|
||||
}
|
||||
_substepsInStep[index] = count;
|
||||
result[index] = count;
|
||||
};
|
||||
push(Step::Initializing, 1);
|
||||
if (_settings.types & Settings::Type::GroupsChannelsMask) {
|
||||
@ -278,14 +306,20 @@ void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
|
||||
if (_settings.types & Settings::Type::AnyChatsMask) {
|
||||
push(Step::Dialogs, info.dialogsCount);
|
||||
}
|
||||
_substepsInStep = std::make_shared<const std::vector<int>>(
|
||||
std::move(result));
|
||||
}
|
||||
|
||||
void Controller::exportNext() {
|
||||
if (!++_stepIndex) {
|
||||
_writer->start(_settings);
|
||||
if (ioCatchError(_writer->start(_settings))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (_stepIndex >= _steps.size()) {
|
||||
_writer->finish();
|
||||
if (ioCatchError(_writer->finish())) {
|
||||
return;
|
||||
}
|
||||
setFinishedState();
|
||||
return;
|
||||
}
|
||||
@ -314,63 +348,82 @@ void Controller::initialize() {
|
||||
}
|
||||
|
||||
void Controller::collectLeftChannels() {
|
||||
_api.requestLeftChannelsList([=](Data::DialogsInfo &&result) {
|
||||
_api.requestLeftChannelsList([=](int count) {
|
||||
setState(stateLeftChannelsList(count));
|
||||
return true;
|
||||
}, [=](Data::DialogsInfo &&result) {
|
||||
_leftChannelsInfo = std::move(result);
|
||||
exportNext();
|
||||
});
|
||||
|
||||
_api.leftChannelsLoadedCount(
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
setState(stateLeftChannelsList(count));
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Controller::collectDialogsList() {
|
||||
_api.requestDialogsList([=](Data::DialogsInfo &&result) {
|
||||
_api.requestDialogsList([=](int count) {
|
||||
setState(stateDialogsList(count));
|
||||
return true;
|
||||
}, [=](Data::DialogsInfo &&result) {
|
||||
_dialogsInfo = std::move(result);
|
||||
exportNext();
|
||||
});
|
||||
|
||||
_api.dialogsLoadedCount(
|
||||
) | rpl::start_with_next([=](int count) {
|
||||
setState(stateDialogsList(count));
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Controller::exportPersonalInfo() {
|
||||
_api.requestPersonalInfo([=](Data::PersonalInfo &&result) {
|
||||
_writer->writePersonal(result);
|
||||
if (ioCatchError(_writer->writePersonal(result))) {
|
||||
return;
|
||||
}
|
||||
exportNext();
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::exportUserpics() {
|
||||
_api.requestUserpics([=](Data::UserpicsInfo &&start) {
|
||||
_writer->writeUserpicsStart(start);
|
||||
if (ioCatchError(_writer->writeUserpicsStart(start))) {
|
||||
return false;
|
||||
}
|
||||
_userpicsWritten = 0;
|
||||
_userpicsCount = start.count;
|
||||
return true;
|
||||
}, [=](DownloadProgress progress) {
|
||||
setState(stateUserpics(progress));
|
||||
return true;
|
||||
}, [=](Data::UserpicsSlice &&slice) {
|
||||
_writer->writeUserpicsSlice(slice);
|
||||
if (ioCatchError(_writer->writeUserpicsSlice(slice))) {
|
||||
return false;
|
||||
}
|
||||
_userpicsWritten += slice.list.size();
|
||||
setState(stateUserpics(DownloadProgress()));
|
||||
return true;
|
||||
}, [=] {
|
||||
_writer->writeUserpicsEnd();
|
||||
if (ioCatchError(_writer->writeUserpicsEnd())) {
|
||||
return;
|
||||
}
|
||||
exportNext();
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::exportContacts() {
|
||||
_api.requestContacts([=](Data::ContactsList &&result) {
|
||||
_writer->writeContactsList(result);
|
||||
if (ioCatchError(_writer->writeContactsList(result))) {
|
||||
return;
|
||||
}
|
||||
exportNext();
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::exportSessions() {
|
||||
_api.requestSessions([=](Data::SessionsList &&result) {
|
||||
_writer->writeSessionsList(result);
|
||||
if (ioCatchError(_writer->writeSessionsList(result))) {
|
||||
return;
|
||||
}
|
||||
exportNext();
|
||||
});
|
||||
}
|
||||
|
||||
void Controller::exportDialogs() {
|
||||
_writer->writeDialogsStart(_dialogsInfo);
|
||||
if (ioCatchError(_writer->writeDialogsStart(_dialogsInfo))) {
|
||||
return;
|
||||
}
|
||||
|
||||
exportNextDialog();
|
||||
}
|
||||
@ -379,22 +432,42 @@ void Controller::exportNextDialog() {
|
||||
const auto index = ++_dialogIndex;
|
||||
if (index < _dialogsInfo.list.size()) {
|
||||
const auto &info = _dialogsInfo.list[index];
|
||||
_writer->writeDialogStart(info);
|
||||
|
||||
_api.requestMessages(info, [=](Data::MessagesSlice &&result) {
|
||||
_writer->writeDialogSlice(result);
|
||||
_api.requestMessages(info, [=](const Data::DialogInfo &info) {
|
||||
if (ioCatchError(_writer->writeDialogStart(info))) {
|
||||
return false;
|
||||
}
|
||||
_messagesWritten = 0;
|
||||
_messagesCount = info.messagesCount;
|
||||
setState(stateDialogs(DownloadProgress()));
|
||||
return true;
|
||||
}, [=](DownloadProgress progress) {
|
||||
setState(stateDialogs(progress));
|
||||
return true;
|
||||
}, [=](Data::MessagesSlice &&result) {
|
||||
if (ioCatchError(_writer->writeDialogSlice(result))) {
|
||||
return false;
|
||||
}
|
||||
_messagesWritten += result.list.size();
|
||||
setState(stateDialogs(DownloadProgress()));
|
||||
return true;
|
||||
}, [=] {
|
||||
_writer->writeDialogEnd();
|
||||
if (ioCatchError(_writer->writeDialogEnd())) {
|
||||
return;
|
||||
}
|
||||
exportNextDialog();
|
||||
});
|
||||
return;
|
||||
}
|
||||
_writer->writeDialogsEnd();
|
||||
if (ioCatchError(_writer->writeDialogsEnd())) {
|
||||
return;
|
||||
}
|
||||
exportNext();
|
||||
}
|
||||
|
||||
void Controller::exportLeftChannels() {
|
||||
_writer->writeLeftChannelsStart(_leftChannelsInfo);
|
||||
if (ioCatchError(_writer->writeLeftChannelsStart(_leftChannelsInfo))) {
|
||||
return;
|
||||
}
|
||||
|
||||
exportNextLeftChannel();
|
||||
}
|
||||
@ -402,18 +475,36 @@ void Controller::exportLeftChannels() {
|
||||
void Controller::exportNextLeftChannel() {
|
||||
const auto index = ++_leftChannelIndex;
|
||||
if (index < _leftChannelsInfo.list.size()) {
|
||||
const auto &chat = _leftChannelsInfo.list[index];
|
||||
_writer->writeLeftChannelStart(chat);
|
||||
|
||||
_api.requestMessages(chat, [=](Data::MessagesSlice &&result) {
|
||||
_writer->writeLeftChannelSlice(result);
|
||||
const auto &info = _leftChannelsInfo.list[index];
|
||||
_api.requestMessages(info, [=](const Data::DialogInfo &info) {
|
||||
if (ioCatchError(_writer->writeLeftChannelStart(info))) {
|
||||
return false;
|
||||
}
|
||||
_messagesWritten = 0;
|
||||
_messagesCount = info.messagesCount;
|
||||
setState(stateLeftChannels(DownloadProgress()));
|
||||
return true;
|
||||
}, [=](DownloadProgress progress) {
|
||||
setState(stateLeftChannels(progress));
|
||||
return true;
|
||||
}, [=](Data::MessagesSlice &&result) {
|
||||
if (ioCatchError(_writer->writeLeftChannelSlice(result))) {
|
||||
return false;
|
||||
}
|
||||
_messagesWritten += result.list.size();
|
||||
setState(stateLeftChannels(DownloadProgress()));
|
||||
return true;
|
||||
}, [=] {
|
||||
_writer->writeLeftChannelEnd();
|
||||
if (ioCatchError(_writer->writeLeftChannelEnd())) {
|
||||
return;
|
||||
}
|
||||
exportNextLeftChannel();
|
||||
});
|
||||
return;
|
||||
}
|
||||
_writer->writeLeftChannelsEnd();
|
||||
if (ioCatchError(_writer->writeLeftChannelsEnd())) {
|
||||
return;
|
||||
}
|
||||
exportNext();
|
||||
}
|
||||
|
||||
@ -451,9 +542,12 @@ ProcessingState Controller::statePersonalInfo() const {
|
||||
return prepareState(Step::PersonalInfo);
|
||||
}
|
||||
|
||||
ProcessingState Controller::stateUserpics() const {
|
||||
ProcessingState Controller::stateUserpics(DownloadProgress progress) const {
|
||||
return prepareState(Step::Userpics, [&](ProcessingState &result) {
|
||||
|
||||
result.entityIndex = _userpicsWritten + progress.itemIndex;
|
||||
result.entityCount = std::max(_userpicsCount, result.entityIndex);
|
||||
result.bytesLoaded = progress.ready;
|
||||
result.bytesCount = progress.total;
|
||||
});
|
||||
}
|
||||
|
||||
@ -465,26 +559,36 @@ ProcessingState Controller::stateSessions() const {
|
||||
return prepareState(Step::Sessions);
|
||||
}
|
||||
|
||||
ProcessingState Controller::stateLeftChannels() const {
|
||||
ProcessingState Controller::stateLeftChannels(
|
||||
DownloadProgress progress) const {
|
||||
const auto step = Step::LeftChannels;
|
||||
return prepareState(step, [&](ProcessingState &result) {
|
||||
//result.entityIndex = processed;
|
||||
//result.entityCount = std::max(processed, substepsInStep(step));
|
||||
result.entityIndex = _leftChannelIndex;
|
||||
result.entityCount = _leftChannelsInfo.list.size();
|
||||
result.itemIndex = _messagesWritten + progress.itemIndex;
|
||||
result.itemCount = std::max(_messagesCount, result.entityIndex);
|
||||
result.bytesLoaded = progress.ready;
|
||||
result.bytesCount = progress.total;
|
||||
});
|
||||
}
|
||||
|
||||
ProcessingState Controller::stateDialogs() const {
|
||||
ProcessingState Controller::stateDialogs(DownloadProgress progress) const {
|
||||
const auto step = Step::Dialogs;
|
||||
return prepareState(step, [&](ProcessingState &result) {
|
||||
//result.entityIndex = processed;
|
||||
//result.entityCount = std::max(processed, substepsInStep(step));
|
||||
result.entityIndex = _dialogIndex;
|
||||
result.entityCount = _dialogsInfo.list.size();
|
||||
result.itemIndex = _messagesWritten + progress.itemIndex;
|
||||
result.itemCount = std::max(_messagesCount, result.entityIndex);
|
||||
result.bytesLoaded = progress.ready;
|
||||
result.bytesCount = progress.total;
|
||||
});
|
||||
}
|
||||
|
||||
int Controller::substepsInStep(Step step) const {
|
||||
Expects(_substepsInStep.size() > static_cast<int>(step));
|
||||
Expects(_substepsInStep != 0);
|
||||
Expects(_substepsInStep->size() > static_cast<int>(step));
|
||||
|
||||
return _substepsInStep[static_cast<int>(step)];
|
||||
return (*_substepsInStep)[static_cast<int>(step)];
|
||||
}
|
||||
|
||||
void Controller::setFinishedState() {
|
||||
|
@ -50,7 +50,7 @@ struct ProcessingState {
|
||||
|
||||
Step step = Step::Initializing;
|
||||
|
||||
std::vector<int> substepsInStep;
|
||||
std::shared_ptr<const std::vector<int>> substepsInStep;
|
||||
|
||||
int entityIndex = 0;
|
||||
int entityCount = 1;
|
||||
@ -68,15 +68,13 @@ struct ProcessingState {
|
||||
|
||||
};
|
||||
|
||||
struct ErrorState {
|
||||
enum class Type {
|
||||
Unknown,
|
||||
API,
|
||||
IO,
|
||||
};
|
||||
Type type = Type::Unknown;
|
||||
base::optional<RPCError> apiError;
|
||||
base::optional<QString> ioErrorPath;
|
||||
struct ApiErrorState {
|
||||
RPCError data;
|
||||
|
||||
};
|
||||
|
||||
struct OutputErrorState {
|
||||
QString path;
|
||||
|
||||
};
|
||||
|
||||
@ -88,7 +86,8 @@ struct FinishedState {
|
||||
using State = base::optional_variant<
|
||||
PasswordCheckState,
|
||||
ProcessingState,
|
||||
ErrorState,
|
||||
ApiErrorState,
|
||||
OutputErrorState,
|
||||
FinishedState>;
|
||||
|
||||
//struct PasswordUpdate {
|
||||
|
@ -25,41 +25,55 @@ struct Settings;
|
||||
|
||||
namespace Output {
|
||||
|
||||
struct Result;
|
||||
|
||||
enum class Format {
|
||||
Text,
|
||||
Yaml,
|
||||
Html,
|
||||
Json,
|
||||
};
|
||||
|
||||
class AbstractWriter {
|
||||
public:
|
||||
virtual bool start(const Settings &settings) = 0;
|
||||
[[nodiscard]] virtual Result start(const Settings &settings) = 0;
|
||||
|
||||
virtual bool writePersonal(const Data::PersonalInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writePersonal(
|
||||
const Data::PersonalInfo &data) = 0;
|
||||
|
||||
virtual bool writeUserpicsStart(const Data::UserpicsInfo &data) = 0;
|
||||
virtual bool writeUserpicsSlice(const Data::UserpicsSlice &data) = 0;
|
||||
virtual bool writeUserpicsEnd() = 0;
|
||||
[[nodiscard]] virtual Result writeUserpicsStart(
|
||||
const Data::UserpicsInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeUserpicsSlice(
|
||||
const Data::UserpicsSlice &data) = 0;
|
||||
[[nodiscard]] virtual Result writeUserpicsEnd() = 0;
|
||||
|
||||
virtual bool writeContactsList(const Data::ContactsList &data) = 0;
|
||||
[[nodiscard]] virtual Result writeContactsList(
|
||||
const Data::ContactsList &data) = 0;
|
||||
|
||||
virtual bool writeSessionsList(const Data::SessionsList &data) = 0;
|
||||
[[nodiscard]] virtual Result writeSessionsList(
|
||||
const Data::SessionsList &data) = 0;
|
||||
|
||||
virtual bool writeDialogsStart(const Data::DialogsInfo &data) = 0;
|
||||
virtual bool writeDialogStart(const Data::DialogInfo &data) = 0;
|
||||
virtual bool writeDialogSlice(const Data::MessagesSlice &data) = 0;
|
||||
virtual bool writeDialogEnd() = 0;
|
||||
virtual bool writeDialogsEnd() = 0;
|
||||
[[nodiscard]] virtual Result writeDialogsStart(
|
||||
const Data::DialogsInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeDialogStart(
|
||||
const Data::DialogInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeDialogSlice(
|
||||
const Data::MessagesSlice &data) = 0;
|
||||
[[nodiscard]] virtual Result writeDialogEnd() = 0;
|
||||
[[nodiscard]] virtual Result writeDialogsEnd() = 0;
|
||||
|
||||
virtual bool writeLeftChannelsStart(const Data::DialogsInfo &data) = 0;
|
||||
virtual bool writeLeftChannelStart(const Data::DialogInfo &data) = 0;
|
||||
virtual bool writeLeftChannelSlice(const Data::MessagesSlice &data) = 0;
|
||||
virtual bool writeLeftChannelEnd() = 0;
|
||||
virtual bool writeLeftChannelsEnd() = 0;
|
||||
[[nodiscard]] virtual Result writeLeftChannelsStart(
|
||||
const Data::DialogsInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeLeftChannelStart(
|
||||
const Data::DialogInfo &data) = 0;
|
||||
[[nodiscard]] virtual Result writeLeftChannelSlice(
|
||||
const Data::MessagesSlice &data) = 0;
|
||||
[[nodiscard]] virtual Result writeLeftChannelEnd() = 0;
|
||||
[[nodiscard]] virtual Result writeLeftChannelsEnd() = 0;
|
||||
|
||||
virtual bool finish() = 0;
|
||||
[[nodiscard]] virtual Result finish() = 0;
|
||||
|
||||
virtual QString mainFilePath() = 0;
|
||||
[[nodiscard]] virtual QString mainFilePath() = 0;
|
||||
|
||||
virtual ~AbstractWriter() = default;
|
||||
|
||||
|
@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "export/output/export_output_file.h"
|
||||
|
||||
#include "export/output/export_output_result.h"
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
@ -26,47 +28,57 @@ bool File::empty() const {
|
||||
return !_offset;
|
||||
}
|
||||
|
||||
File::Result File::writeBlock(const QByteArray &block) {
|
||||
Result File::writeBlock(const QByteArray &block) {
|
||||
const auto result = writeBlockAttempt(block);
|
||||
if (result != Result::Success) {
|
||||
if (!result) {
|
||||
_file.clear();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
File::Result File::writeBlockAttempt(const QByteArray &block) {
|
||||
if (const auto result = reopen(); result != Result::Success) {
|
||||
Result File::writeBlockAttempt(const QByteArray &block) {
|
||||
if (const auto result = reopen(); !result) {
|
||||
return result;
|
||||
}
|
||||
return (_file->write(block) == block.size() && _file->flush())
|
||||
? Result::Success
|
||||
: Result::Error;
|
||||
if (_file->write(block) == block.size() && _file->flush()) {
|
||||
_offset += block.size();
|
||||
return Result::Success();
|
||||
}
|
||||
return error();
|
||||
}
|
||||
|
||||
File::Result File::reopen() {
|
||||
Result File::reopen() {
|
||||
if (_file && _file->isOpen()) {
|
||||
return Result::Success;
|
||||
return Result::Success();
|
||||
}
|
||||
_file.emplace(_path);
|
||||
if (_file->exists()) {
|
||||
if (_file->size() < _offset) {
|
||||
return Result::FatalError;
|
||||
return fatalError();
|
||||
} else if (!_file->resize(_offset)) {
|
||||
return Result::Error;
|
||||
return error();
|
||||
}
|
||||
} else if (_offset > 0) {
|
||||
return Result::FatalError;
|
||||
return fatalError();
|
||||
}
|
||||
if (_file->open(QIODevice::Append)) {
|
||||
return Result::Success;
|
||||
return Result::Success();
|
||||
}
|
||||
const auto info = QFileInfo(_path);
|
||||
const auto dir = info.absoluteDir();
|
||||
return (!dir.exists()
|
||||
&& dir.mkpath(dir.absolutePath())
|
||||
&& _file->open(QIODevice::Append))
|
||||
? Result::Success
|
||||
: Result::Error;
|
||||
? Result::Success()
|
||||
: error();
|
||||
}
|
||||
|
||||
Result File::error() const {
|
||||
return Result(Result::Type::Error, _path);
|
||||
}
|
||||
|
||||
Result File::fatalError() const {
|
||||
return Result(Result::Type::FatalError, _path);
|
||||
}
|
||||
|
||||
QString File::PrepareRelativePath(
|
||||
|
@ -16,27 +16,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
struct Result;
|
||||
|
||||
class File {
|
||||
public:
|
||||
File(const QString &path);
|
||||
|
||||
int size() const;
|
||||
bool empty() const;
|
||||
[[nodiscard]] int size() const;
|
||||
[[nodiscard]] bool empty() const;
|
||||
|
||||
enum class Result {
|
||||
Success,
|
||||
Error,
|
||||
FatalError,
|
||||
};
|
||||
Result writeBlock(const QByteArray &block);
|
||||
[[nodiscard]] Result writeBlock(const QByteArray &block);
|
||||
|
||||
static QString PrepareRelativePath(
|
||||
[[nodiscard]] static QString PrepareRelativePath(
|
||||
const QString &folder,
|
||||
const QString &suggested);
|
||||
|
||||
private:
|
||||
Result reopen();
|
||||
Result writeBlockAttempt(const QByteArray &block);
|
||||
[[nodiscard]] Result reopen();
|
||||
[[nodiscard]] Result writeBlockAttempt(const QByteArray &block);
|
||||
|
||||
[[nodiscard]] Result error() const;
|
||||
[[nodiscard]] Result fatalError() const;
|
||||
|
||||
QString _path;
|
||||
int _offset = 0;
|
||||
|
48
Telegram/SourceFiles/export/output/export_output_result.h
Normal file
48
Telegram/SourceFiles/export/output/export_output_result.h
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace Export {
|
||||
namespace Output {
|
||||
|
||||
struct Result {
|
||||
enum class Type : char {
|
||||
Success,
|
||||
Error,
|
||||
FatalError
|
||||
};
|
||||
|
||||
Result(Type type, QString path) : type(type), path(path) {
|
||||
}
|
||||
|
||||
static Result Success() {
|
||||
return Result(Type::Success, QString());
|
||||
}
|
||||
|
||||
bool isSuccess() const {
|
||||
return type == Type::Success;
|
||||
}
|
||||
bool isError() const {
|
||||
return (type == Type::Error) || (type == Type::FatalError);
|
||||
}
|
||||
bool isFatalError() const {
|
||||
return (type == Type::FatalError);
|
||||
}
|
||||
explicit operator bool() const {
|
||||
return isSuccess();
|
||||
}
|
||||
|
||||
QString path;
|
||||
Type type;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Output
|
||||
} // namespace Export
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "export/output/export_output_text.h"
|
||||
|
||||
#include "export/output/export_output_result.h"
|
||||
#include "export/data/export_data_types.h"
|
||||
#include "core/utils.h"
|
||||
|
||||
@ -431,16 +432,16 @@ QByteArray SerializeMessage(
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TextWriter::start(const Settings &settings) {
|
||||
Result TextWriter::start(const Settings &settings) {
|
||||
Expects(settings.path.endsWith('/'));
|
||||
|
||||
_settings = base::duplicate(settings);
|
||||
_result = fileWithRelativePath(mainFileRelativePath());
|
||||
return true;
|
||||
_summary = fileWithRelativePath(mainFileRelativePath());
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
bool TextWriter::writePersonal(const Data::PersonalInfo &data) {
|
||||
Expects(_result != nullptr);
|
||||
Result TextWriter::writePersonal(const Data::PersonalInfo &data) {
|
||||
Expects(_summary != nullptr);
|
||||
|
||||
const auto &info = data.user.info;
|
||||
const auto serialized = "Personal information"
|
||||
@ -454,25 +455,25 @@ bool TextWriter::writePersonal(const Data::PersonalInfo &data) {
|
||||
{ "Bio", data.bio },
|
||||
})
|
||||
+ kLineBreak;
|
||||
return _result->writeBlock(serialized) == File::Result::Success;
|
||||
return _summary->writeBlock(serialized);
|
||||
}
|
||||
|
||||
bool TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
|
||||
Expects(_result != nullptr);
|
||||
Result TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
|
||||
Expects(_summary != nullptr);
|
||||
|
||||
_userpicsCount = data.count;
|
||||
if (!_userpicsCount) {
|
||||
return true;
|
||||
return Result::Success();
|
||||
}
|
||||
const auto serialized = "Personal photos "
|
||||
"(" + Data::NumberToString(_userpicsCount) + ")"
|
||||
+ kLineBreak
|
||||
+ kLineBreak;
|
||||
return _result->writeBlock(serialized) == File::Result::Success;
|
||||
return _summary->writeBlock(serialized);
|
||||
}
|
||||
|
||||
bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
|
||||
Expects(_result != nullptr);
|
||||
Result TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
|
||||
Expects(_summary != nullptr);
|
||||
Expects(!data.list.empty());
|
||||
|
||||
auto lines = QByteArray();
|
||||
@ -489,22 +490,22 @@ bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
|
||||
}
|
||||
lines.append(kLineBreak);
|
||||
}
|
||||
return _result->writeBlock(lines) == File::Result::Success;
|
||||
return _summary->writeBlock(lines);
|
||||
}
|
||||
|
||||
bool TextWriter::writeUserpicsEnd() {
|
||||
Expects(_result != nullptr);
|
||||
Result TextWriter::writeUserpicsEnd() {
|
||||
Expects(_summary != nullptr);
|
||||
|
||||
return (_userpicsCount > 0)
|
||||
? _result->writeBlock(kLineBreak) == File::Result::Success
|
||||
: true;
|
||||
? _summary->writeBlock(kLineBreak)
|
||||
: Result::Success();
|
||||
}
|
||||
|
||||
bool TextWriter::writeContactsList(const Data::ContactsList &data) {
|
||||
Expects(_result != nullptr);
|
||||
Result TextWriter::writeContactsList(const Data::ContactsList &data) {
|
||||
Expects(_summary != nullptr);
|
||||
|
||||
if (data.list.empty()) {
|
||||
return true;
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
const auto file = fileWithRelativePath("contacts.txt");
|
||||
@ -529,22 +530,22 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
|
||||
}
|
||||
}
|
||||
const auto full = JoinList(kLineBreak, list);
|
||||
if (file->writeBlock(full) != File::Result::Success) {
|
||||
return false;
|
||||
if (const auto result = file->writeBlock(full); !result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto header = "Contacts "
|
||||
"(" + Data::NumberToString(data.list.size()) + ") - contacts.txt"
|
||||
+ kLineBreak
|
||||
+ kLineBreak;
|
||||
return _result->writeBlock(header) == File::Result::Success;
|
||||
return _summary->writeBlock(header);
|
||||
}
|
||||
|
||||
bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
|
||||
Expects(_result != nullptr);
|
||||
Result TextWriter::writeSessionsList(const Data::SessionsList &data) {
|
||||
Expects(_summary != nullptr);
|
||||
|
||||
if (data.list.empty()) {
|
||||
return true;
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
const auto file = fileWithRelativePath("sessions.txt");
|
||||
@ -570,67 +571,68 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
|
||||
}));
|
||||
}
|
||||
const auto full = JoinList(kLineBreak, list);
|
||||
if (file->writeBlock(full) != File::Result::Success) {
|
||||
return false;
|
||||
if (const auto result = file->writeBlock(full); !result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto header = "Sessions "
|
||||
"(" + Data::NumberToString(data.list.size()) + ") - sessions.txt"
|
||||
+ kLineBreak
|
||||
+ kLineBreak;
|
||||
return _result->writeBlock(header) == File::Result::Success;
|
||||
return _summary->writeBlock(header);
|
||||
}
|
||||
|
||||
bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
|
||||
Result TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
|
||||
return writeChatsStart(data, "Chats", "chats.txt");
|
||||
}
|
||||
|
||||
bool TextWriter::writeDialogStart(const Data::DialogInfo &data) {
|
||||
Result TextWriter::writeDialogStart(const Data::DialogInfo &data) {
|
||||
return writeChatStart(data);
|
||||
}
|
||||
|
||||
bool TextWriter::writeDialogSlice(const Data::MessagesSlice &data) {
|
||||
Result TextWriter::writeDialogSlice(const Data::MessagesSlice &data) {
|
||||
return writeChatSlice(data);
|
||||
}
|
||||
|
||||
bool TextWriter::writeDialogEnd() {
|
||||
Result TextWriter::writeDialogEnd() {
|
||||
return writeChatEnd();
|
||||
}
|
||||
|
||||
bool TextWriter::writeDialogsEnd() {
|
||||
return true;
|
||||
Result TextWriter::writeDialogsEnd() {
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
bool TextWriter::writeLeftChannelsStart(const Data::DialogsInfo &data) {
|
||||
Result TextWriter::writeLeftChannelsStart(const Data::DialogsInfo &data) {
|
||||
return writeChatsStart(data, "Left chats", "left_chats.txt");
|
||||
}
|
||||
|
||||
bool TextWriter::writeLeftChannelStart(const Data::DialogInfo &data) {
|
||||
Result TextWriter::writeLeftChannelStart(const Data::DialogInfo &data) {
|
||||
return writeChatStart(data);
|
||||
}
|
||||
|
||||
bool TextWriter::writeLeftChannelSlice(const Data::MessagesSlice &data) {
|
||||
Result TextWriter::writeLeftChannelSlice(const Data::MessagesSlice &data) {
|
||||
return writeChatSlice(data);
|
||||
}
|
||||
|
||||
bool TextWriter::writeLeftChannelEnd() {
|
||||
Result TextWriter::writeLeftChannelEnd() {
|
||||
return writeChatEnd();
|
||||
}
|
||||
|
||||
bool TextWriter::writeLeftChannelsEnd() {
|
||||
return true;
|
||||
Result TextWriter::writeLeftChannelsEnd() {
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
bool TextWriter::writeChatsStart(
|
||||
Result TextWriter::writeChatsStart(
|
||||
const Data::DialogsInfo &data,
|
||||
const QByteArray &listName,
|
||||
const QString &fileName) {
|
||||
Expects(_result != nullptr);
|
||||
Expects(_summary != nullptr);
|
||||
|
||||
if (data.list.empty()) {
|
||||
return true;
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
_dialogIndex = 0;
|
||||
_dialogsCount = data.list.size();
|
||||
|
||||
using Type = Data::DialogInfo::Type;
|
||||
@ -676,8 +678,8 @@ bool TextWriter::writeChatsStart(
|
||||
}));
|
||||
}
|
||||
const auto full = JoinList(kLineBreak, list);
|
||||
if (file->writeBlock(full) != File::Result::Success) {
|
||||
return false;
|
||||
if (const auto result = file->writeBlock(full); !result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto header = listName + " "
|
||||
@ -685,10 +687,10 @@ bool TextWriter::writeChatsStart(
|
||||
+ fileName.toUtf8()
|
||||
+ kLineBreak
|
||||
+ kLineBreak;
|
||||
return _result->writeBlock(header) == File::Result::Success;
|
||||
return _summary->writeBlock(header);
|
||||
}
|
||||
|
||||
bool TextWriter::writeChatStart(const Data::DialogInfo &data) {
|
||||
Result TextWriter::writeChatStart(const Data::DialogInfo &data) {
|
||||
Expects(_chat == nullptr);
|
||||
Expects(_dialogIndex < _dialogsCount);
|
||||
|
||||
@ -697,10 +699,10 @@ bool TextWriter::writeChatStart(const Data::DialogInfo &data) {
|
||||
_chat = fileWithRelativePath(data.relativePath + "messages.txt");
|
||||
_dialogEmpty = true;
|
||||
_dialogOnlyMy = data.onlyMyMessages;
|
||||
return true;
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
bool TextWriter::writeChatSlice(const Data::MessagesSlice &data) {
|
||||
Result TextWriter::writeChatSlice(const Data::MessagesSlice &data) {
|
||||
Expects(_chat != nullptr);
|
||||
Expects(!data.list.empty());
|
||||
|
||||
@ -717,23 +719,26 @@ bool TextWriter::writeChatSlice(const Data::MessagesSlice &data) {
|
||||
const auto full = _chat->empty()
|
||||
? JoinList(kLineBreak, list)
|
||||
: kLineBreak + JoinList(kLineBreak, list);
|
||||
return _chat->writeBlock(full) == File::Result::Success;
|
||||
return _chat->writeBlock(full);
|
||||
}
|
||||
|
||||
bool TextWriter::writeChatEnd() {
|
||||
Result TextWriter::writeChatEnd() {
|
||||
Expects(_chat != nullptr);
|
||||
|
||||
if (_dialogEmpty) {
|
||||
_chat->writeBlock(_dialogOnlyMy
|
||||
const auto result = _chat->writeBlock(_dialogOnlyMy
|
||||
? "No outgoing messages in this chat."
|
||||
: "No messages in this chat.");
|
||||
if (!result) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
_chat = nullptr;
|
||||
return true;
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
bool TextWriter::finish() {
|
||||
return true;
|
||||
Result TextWriter::finish() {
|
||||
return Result::Success();
|
||||
}
|
||||
|
||||
QString TextWriter::mainFilePath() {
|
||||
|
@ -16,31 +16,31 @@ namespace Output {
|
||||
|
||||
class TextWriter : public AbstractWriter {
|
||||
public:
|
||||
bool start(const Settings &settings) override;
|
||||
Result start(const Settings &settings) override;
|
||||
|
||||
bool writePersonal(const Data::PersonalInfo &data) override;
|
||||
Result writePersonal(const Data::PersonalInfo &data) override;
|
||||
|
||||
bool writeUserpicsStart(const Data::UserpicsInfo &data) override;
|
||||
bool writeUserpicsSlice(const Data::UserpicsSlice &data) override;
|
||||
bool writeUserpicsEnd() override;
|
||||
Result writeUserpicsStart(const Data::UserpicsInfo &data) override;
|
||||
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
|
||||
Result writeUserpicsEnd() override;
|
||||
|
||||
bool writeContactsList(const Data::ContactsList &data) override;
|
||||
Result writeContactsList(const Data::ContactsList &data) override;
|
||||
|
||||
bool writeSessionsList(const Data::SessionsList &data) override;
|
||||
Result writeSessionsList(const Data::SessionsList &data) override;
|
||||
|
||||
bool writeDialogsStart(const Data::DialogsInfo &data) override;
|
||||
bool writeDialogStart(const Data::DialogInfo &data) override;
|
||||
bool writeDialogSlice(const Data::MessagesSlice &data) override;
|
||||
bool writeDialogEnd() override;
|
||||
bool writeDialogsEnd() override;
|
||||
Result writeDialogsStart(const Data::DialogsInfo &data) override;
|
||||
Result writeDialogStart(const Data::DialogInfo &data) override;
|
||||
Result writeDialogSlice(const Data::MessagesSlice &data) override;
|
||||
Result writeDialogEnd() override;
|
||||
Result writeDialogsEnd() override;
|
||||
|
||||
bool writeLeftChannelsStart(const Data::DialogsInfo &data) override;
|
||||
bool writeLeftChannelStart(const Data::DialogInfo &data) override;
|
||||
bool writeLeftChannelSlice(const Data::MessagesSlice &data) override;
|
||||
bool writeLeftChannelEnd() override;
|
||||
bool writeLeftChannelsEnd() override;
|
||||
Result writeLeftChannelsStart(const Data::DialogsInfo &data) override;
|
||||
Result writeLeftChannelStart(const Data::DialogInfo &data) override;
|
||||
Result writeLeftChannelSlice(const Data::MessagesSlice &data) override;
|
||||
Result writeLeftChannelEnd() override;
|
||||
Result writeLeftChannelsEnd() override;
|
||||
|
||||
bool finish() override;
|
||||
Result finish() override;
|
||||
|
||||
QString mainFilePath() override;
|
||||
|
||||
@ -49,17 +49,17 @@ private:
|
||||
QString pathWithRelativePath(const QString &path) const;
|
||||
std::unique_ptr<File> fileWithRelativePath(const QString &path) const;
|
||||
|
||||
bool writeChatsStart(
|
||||
Result writeChatsStart(
|
||||
const Data::DialogsInfo &data,
|
||||
const QByteArray &listName,
|
||||
const QString &fileName);
|
||||
bool writeChatStart(const Data::DialogInfo &data);
|
||||
bool writeChatSlice(const Data::MessagesSlice &data);
|
||||
bool writeChatEnd();
|
||||
Result writeChatStart(const Data::DialogInfo &data);
|
||||
Result writeChatSlice(const Data::MessagesSlice &data);
|
||||
Result writeChatEnd();
|
||||
|
||||
Settings _settings;
|
||||
|
||||
std::unique_ptr<File> _result;
|
||||
std::unique_ptr<File> _summary;
|
||||
int _userpicsCount = 0;
|
||||
|
||||
int _dialogsCount = 0;
|
||||
@ -67,10 +67,6 @@ private:
|
||||
bool _dialogOnlyMy = false;
|
||||
bool _dialogEmpty = true;
|
||||
|
||||
int _leftChannelsCount = 0;
|
||||
int _leftChannelIndex = 0;
|
||||
bool _leftChannelEmpty = true;
|
||||
|
||||
std::unique_ptr<File> _chat;
|
||||
|
||||
};
|
||||
|
@ -37,3 +37,8 @@ exportFileSizeLabel: LabelSimple(defaultLabelSimple) {
|
||||
}
|
||||
exportFileSizePadding: margins(22px, 8px, 22px, 8px);
|
||||
exportFileSizeLabelBottom: 18px;
|
||||
exportErrorLabel: FlatLabel(boxLabel) {
|
||||
minWidth: 175px;
|
||||
align: align(top);
|
||||
textFg: boxTextFgError;
|
||||
}
|
||||
|
53
Telegram/SourceFiles/export/view/export_view_content.cpp
Normal file
53
Telegram/SourceFiles/export/view/export_view_content.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "export/view/export_view_content.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
|
||||
namespace Export {
|
||||
namespace View {
|
||||
|
||||
Content ContentFromState(const ProcessingState &state) {
|
||||
using Step = ProcessingState::Step;
|
||||
|
||||
auto result = Content();
|
||||
const auto push = [&](
|
||||
const QString &id,
|
||||
const QString &label,
|
||||
const QString &info,
|
||||
float64 progress) {
|
||||
result.rows.push_back({ id, label, info, progress });
|
||||
};
|
||||
switch (state.step) {
|
||||
case Step::Initializing:
|
||||
case Step::LeftChannelsList:
|
||||
case Step::DialogsList:
|
||||
case Step::PersonalInfo:
|
||||
case Step::Userpics:
|
||||
case Step::Contacts:
|
||||
case Step::Sessions:
|
||||
case Step::LeftChannels:
|
||||
case Step::Dialogs:
|
||||
push("init", lang(lng_export_state_initializing), QString(), 0.);
|
||||
if (state.entityCount > 0) {
|
||||
push("entity", QString(), QString::number(state.entityIndex) + '/' + QString::number(state.entityCount), 0.);
|
||||
}
|
||||
if (state.itemCount > 0) {
|
||||
push("item", QString(), QString::number(state.itemIndex) + '/' + QString::number(state.itemCount), 0.);
|
||||
}
|
||||
if (state.bytesCount > 0) {
|
||||
push("bytes", QString(), QString::number(state.bytesLoaded) + '/' + QString::number(state.bytesCount), 0.);
|
||||
}
|
||||
break;
|
||||
default: Unexpected("Step in ContentFromState.");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace View
|
||||
} // namespace Export
|
41
Telegram/SourceFiles/export/view/export_view_content.h
Normal file
41
Telegram/SourceFiles/export/view/export_view_content.h
Normal file
@ -0,0 +1,41 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "export/export_controller.h"
|
||||
|
||||
namespace Export {
|
||||
namespace View {
|
||||
|
||||
struct Content {
|
||||
struct Row {
|
||||
QString id;
|
||||
QString label;
|
||||
QString info;
|
||||
float64 progress = 0.;
|
||||
};
|
||||
|
||||
std::vector<Row> rows;
|
||||
|
||||
};
|
||||
|
||||
Content ContentFromState(const ProcessingState &state);
|
||||
|
||||
inline auto ContentFromState(rpl::producer<State> state) {
|
||||
return std::move(
|
||||
state
|
||||
) | rpl::filter([](const State &state) {
|
||||
return state.template is<ProcessingState>();
|
||||
}) | rpl::map([](const State &state) {
|
||||
return ContentFromState(
|
||||
state.template get_unchecked<ProcessingState>());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace View
|
||||
} // namespace Export
|
@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "export/view/export_view_panel_controller.h"
|
||||
|
||||
#include "export/view/export_view_settings.h"
|
||||
#include "export/view/export_view_progress.h"
|
||||
#include "export/view/export_view_done.h"
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "ui/widgets/separate_panel.h"
|
||||
#include "ui/wrap/padding_wrap.h"
|
||||
#include "lang/lang_keys.h"
|
||||
#include "core/file_utilities.h"
|
||||
#include "styles/style_export.h"
|
||||
@ -43,6 +46,9 @@ void PanelController::showSettings() {
|
||||
|
||||
settings->startClicks(
|
||||
) | rpl::start_with_next([=](const Settings &settings) {
|
||||
_panel->showInner(base::make_unique_q<ProgressWidget>(
|
||||
_panel.get(),
|
||||
ContentFromState(_process->state())));
|
||||
_process->startExport(settings);
|
||||
}, settings->lifetime());
|
||||
|
||||
@ -54,6 +60,34 @@ void PanelController::showSettings() {
|
||||
_panel->showInner(std::move(settings));
|
||||
}
|
||||
|
||||
void PanelController::showError(const ApiErrorState &error) {
|
||||
showError("API Error happened :(\n"
|
||||
+ QString::number(error.data.code()) + ": " + error.data.type()
|
||||
+ "\n" + error.data.description());
|
||||
}
|
||||
|
||||
void PanelController::showError(const OutputErrorState &error) {
|
||||
showError("Disk Error happened :(\n"
|
||||
"Could not write path:\n" + error.path);
|
||||
}
|
||||
|
||||
void PanelController::showError(const QString &text) {
|
||||
auto container = base::make_unique_q<Ui::PaddingWrap<Ui::FlatLabel>>(
|
||||
_panel.get(),
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
_panel.get(),
|
||||
text,
|
||||
Ui::FlatLabel::InitType::Simple,
|
||||
st::exportErrorLabel),
|
||||
style::margins(0, st::exportPanelSize.height() / 4, 0, 0));
|
||||
container->widthValue(
|
||||
) | rpl::start_with_next([label = container->entity()](int width) {
|
||||
label->resize(width, label->height());
|
||||
}, container->lifetime());
|
||||
|
||||
_panel->showInner(std::move(container));
|
||||
}
|
||||
|
||||
rpl::producer<> PanelController::closed() const {
|
||||
return _panelCloseEvents.events(
|
||||
) | rpl::flatten_latest(
|
||||
@ -67,7 +101,11 @@ void PanelController::updateState(State &&state) {
|
||||
createPanel();
|
||||
}
|
||||
_state = std::move(state);
|
||||
if (const auto finished = base::get_if<FinishedState>(&_state)) {
|
||||
if (const auto apiError = base::get_if<ApiErrorState>(&_state)) {
|
||||
showError(*apiError);
|
||||
} else if (const auto error = base::get_if<OutputErrorState>(&_state)) {
|
||||
showError(*error);
|
||||
} else if (const auto finished = base::get_if<FinishedState>(&_state)) {
|
||||
const auto path = finished->path;
|
||||
|
||||
auto done = base::make_unique_q<DoneWidget>(_panel.get());
|
||||
|
@ -31,6 +31,9 @@ private:
|
||||
void createPanel();
|
||||
void updateState(State &&state);
|
||||
void showSettings();
|
||||
void showError(const ApiErrorState &error);
|
||||
void showError(const OutputErrorState &error);
|
||||
void showError(const QString &text);
|
||||
|
||||
not_null<ControllerWrap*> _process;
|
||||
|
||||
|
46
Telegram/SourceFiles/export/view/export_view_progress.cpp
Normal file
46
Telegram/SourceFiles/export/view/export_view_progress.cpp
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "export/view/export_view_progress.h"
|
||||
|
||||
#include "ui/widgets/labels.h"
|
||||
#include "styles/style_boxes.h"
|
||||
|
||||
namespace Export {
|
||||
namespace View {
|
||||
|
||||
ProgressWidget::ProgressWidget(
|
||||
QWidget *parent,
|
||||
rpl::producer<Content> content)
|
||||
: RpWidget(parent) {
|
||||
const auto label = Ui::CreateChild<Ui::FlatLabel>(this, st::boxLabel);
|
||||
sizeValue(
|
||||
) | rpl::start_with_next([=](QSize size) {
|
||||
label->setGeometry(QRect(QPoint(), size));
|
||||
}, label->lifetime());
|
||||
std::move(
|
||||
content
|
||||
) | rpl::start_with_next([=](Content &&content) {
|
||||
auto text = QString();
|
||||
for (const auto &row : content.rows) {
|
||||
text += row.id + ' ' + row.info + ' ' + row.label + '\n';
|
||||
}
|
||||
label->setText(text);
|
||||
updateState(std::move(content));
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void ProgressWidget::updateState(Content &&content) {
|
||||
_content = std::move(content);
|
||||
|
||||
|
||||
}
|
||||
|
||||
ProgressWidget::~ProgressWidget() = default;
|
||||
|
||||
} // namespace View
|
||||
} // namespace Export
|
35
Telegram/SourceFiles/export/view/export_view_progress.h
Normal file
35
Telegram/SourceFiles/export/view/export_view_progress.h
Normal file
@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/rp_widget.h"
|
||||
#include "export/view/export_view_content.h"
|
||||
|
||||
namespace Export {
|
||||
namespace View {
|
||||
|
||||
class ProgressWidget : public Ui::RpWidget {
|
||||
public:
|
||||
ProgressWidget(
|
||||
QWidget *parent,
|
||||
rpl::producer<Content> content);
|
||||
|
||||
~ProgressWidget();
|
||||
|
||||
private:
|
||||
void updateState(Content &&content);
|
||||
|
||||
Content _content;
|
||||
|
||||
class Row;
|
||||
std::vector<base::unique_qptr<Row>> _rows;
|
||||
|
||||
};
|
||||
|
||||
} // namespace View
|
||||
} // namespace Export
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
#include "mtproto/mtp_instance.h"
|
||||
#include "mtproto/rpc_sender.h"
|
||||
#include "mtproto/session.h"
|
||||
|
||||
namespace MTP {
|
||||
|
||||
@ -178,7 +179,13 @@ void ConcurrentSender::senderRequestDone(
|
||||
mtpRequestId requestId,
|
||||
bytes::const_span result) {
|
||||
if (auto handlers = _requests.take(requestId)) {
|
||||
std::move(handlers->done)(requestId, result);
|
||||
try {
|
||||
std::move(handlers->done)(requestId, result);
|
||||
} catch (Exception &e) {
|
||||
std::move(handlers->fail)(requestId, internal::rpcClientError(
|
||||
"RESPONSE_PARSE_FAILED",
|
||||
QString("exception text: ") + e.what()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,14 +203,11 @@ void ConcurrentSender::RequestBuilder::setDoneHandler(
|
||||
_handlers.done = [handler = std::move(invoke)](
|
||||
mtpRequestId requestId,
|
||||
bytes::const_span result) mutable {
|
||||
try {
|
||||
auto from = reinterpret_cast<const mtpPrime*>(result.data());
|
||||
const auto end = from + result.size() / sizeof(mtpPrime);
|
||||
Response data;
|
||||
data.read(from, end);
|
||||
std::move(handler)(requestId, std::move(data));
|
||||
} catch (...) {
|
||||
}
|
||||
auto from = reinterpret_cast<const mtpPrime*>(result.data());
|
||||
const auto end = from + result.size() / sizeof(mtpPrime);
|
||||
Response data;
|
||||
data.read(from, end);
|
||||
std::move(handler)(requestId, std::move(data));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -225,10 +225,14 @@
|
||||
<(src_loc)/dialogs/dialogs_search_from_controllers.h
|
||||
<(src_loc)/dialogs/dialogs_widget.cpp
|
||||
<(src_loc)/dialogs/dialogs_widget.h
|
||||
<(src_loc)/export/view/export_view_content.cpp
|
||||
<(src_loc)/export/view/export_view_content.h
|
||||
<(src_loc)/export/view/export_view_done.cpp
|
||||
<(src_loc)/export/view/export_view_done.h
|
||||
<(src_loc)/export/view/export_view_panel_controller.cpp
|
||||
<(src_loc)/export/view/export_view_panel_controller.h
|
||||
<(src_loc)/export/view/export_view_progress.cpp
|
||||
<(src_loc)/export/view/export_view_progress.h
|
||||
<(src_loc)/export/view/export_view_settings.cpp
|
||||
<(src_loc)/export/view/export_view_settings.h
|
||||
<(src_loc)/history/admin_log/history_admin_log_filter.cpp
|
||||
|
Loading…
Reference in New Issue
Block a user