Display errors in export UI.

All errors are now fatal errors :(
This commit is contained in:
John Preston 2018-06-19 11:42:21 +01:00
parent 7d4e23448e
commit 5f01751660
23 changed files with 844 additions and 284 deletions

View File

@ -1654,7 +1654,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_bad_name" = "Please use latin characters only."; "lng_passport_bad_name" = "Please use latin characters only.";
"lng_export_title" = "Personal data export"; "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_contacts" = "Contacts list";
"lng_export_option_sessions" = "Sessions list"; "lng_export_option_sessions" = "Sessions list";
"lng_export_header_chats" = "Chats export settings"; "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_files" = "Files";
"lng_export_option_size_limit" = "Size limit: {size}"; "lng_export_option_size_limit" = "Size limit: {size}";
"lng_export_start" = "Export"; "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 // Wnd specific

View File

@ -250,6 +250,21 @@ QString CleanDocumentName(QString name) {
return 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( Document ParseDocument(
const MTPDocument &data, const MTPDocument &data,
const QString &suggestedFolder, const QString &suggestedFolder,
@ -267,6 +282,7 @@ Document ParseDocument(
result.mime = ParseString(data.vmime_type); result.mime = ParseString(data.vmime_type);
ParseAttributes(result, data.vattributes); ParseAttributes(result, data.vattributes);
result.file.suggestedPath = suggestedFolder result.file.suggestedPath = suggestedFolder
+ DocumentFolder(result) + '/'
+ CleanDocumentName( + CleanDocumentName(
ComputeDocumentName(result, date ? date : result.date)); ComputeDocumentName(result, date ? date : result.date));
}, [&](const MTPDdocumentEmpty &data) { }, [&](const MTPDdocumentEmpty &data) {
@ -548,10 +564,7 @@ Media ParseMedia(
result.content = UnsupportedMedia(); result.content = UnsupportedMedia();
}, [&](const MTPDmessageMediaDocument &data) { }, [&](const MTPDmessageMediaDocument &data) {
result.content = data.has_document() result.content = data.has_document()
? ParseDocument( ? ParseDocument(data.vdocument, folder, date)
data.vdocument,
folder + "Files/",
date)
: Document(); : Document();
if (data.has_ttl_seconds()) { if (data.has_ttl_seconds()) {
result.ttl = data.vttl_seconds.v; result.ttl = data.vttl_seconds.v;

View File

@ -427,6 +427,9 @@ struct DialogInfo {
bool onlyMyMessages = false; bool onlyMyMessages = false;
QString relativePath; QString relativePath;
// Filled when requesting dialog messages.
int messagesCount = 0;
}; };
struct DialogsInfo { struct DialogsInfo {

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/export_settings.h" #include "export/export_settings.h"
#include "export/data/export_data_types.h" #include "export/data/export_data_types.h"
#include "export/output/export_output_result.h"
#include "export/output/export_output_file.h" #include "export/output/export_output_file.h"
#include "mtproto/rpc_sender.h" #include "mtproto/rpc_sender.h"
#include "base/value_ordering.h" #include "base/value_ordering.h"
@ -114,11 +115,13 @@ struct ApiWrap::StartProcess {
}; };
struct ApiWrap::UserpicsProcess { struct ApiWrap::UserpicsProcess {
FnMut<void(Data::UserpicsInfo&&)> start; FnMut<bool(Data::UserpicsInfo&&)> start;
Fn<void(Data::UserpicsSlice&&)> handleSlice; Fn<bool(DownloadProgress)> fileProgress;
Fn<bool(Data::UserpicsSlice&&)> handleSlice;
FnMut<void()> finish; FnMut<void()> finish;
base::optional<Data::UserpicsSlice> slice; base::optional<Data::UserpicsSlice> slice;
uint64 maxId = 0;
bool lastSlice = false; bool lastSlice = false;
int fileIndex = -1; int fileIndex = -1;
@ -130,6 +133,7 @@ struct ApiWrap::FileProcess {
Output::File file; Output::File file;
QString relativePath; QString relativePath;
Fn<bool(FileProgress)> progress;
FnMut<void(const QString &relativePath)> done; FnMut<void(const QString &relativePath)> done;
Data::FileLocation location; Data::FileLocation location;
@ -144,18 +148,25 @@ struct ApiWrap::FileProcess {
}; };
struct ApiWrap::FileProgress {
int ready = 0;
int total = 0;
};
struct ApiWrap::LeftChannelsProcess { struct ApiWrap::LeftChannelsProcess {
Fn<bool(int count)> progress;
FnMut<void(Data::DialogsInfo&&)> done; FnMut<void(Data::DialogsInfo&&)> done;
Data::DialogsInfo info; Data::DialogsInfo info;
rpl::variable<int> count;
int fullCount = 0; int fullCount = 0;
int offset = 0;
bool finished = false; bool finished = false;
}; };
struct ApiWrap::DialogsProcess { struct ApiWrap::DialogsProcess {
Fn<bool(int count)> progress;
FnMut<void(Data::DialogsInfo&&)> done; FnMut<void(Data::DialogsInfo&&)> done;
Data::DialogsInfo info; Data::DialogsInfo info;
@ -164,14 +175,14 @@ struct ApiWrap::DialogsProcess {
int32 offsetId = 0; int32 offsetId = 0;
MTPInputPeer offsetPeer = MTP_inputPeerEmpty(); MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
rpl::variable<int> count;
}; };
struct ApiWrap::ChatProcess { struct ApiWrap::ChatProcess {
Data::DialogInfo info; 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; FnMut<void()> done;
int32 offsetId = 1; int32 offsetId = 1;
@ -247,6 +258,10 @@ rpl::producer<RPCError> ApiWrap::errors() const {
return _errors.events(); return _errors.events();
} }
rpl::producer<Output::Result> ApiWrap::ioErrors() const {
return _ioErrors.events();
}
void ApiWrap::startExport( void ApiWrap::startExport(
const Settings &settings, const Settings &settings,
FnMut<void(StartInfo)> done) { FnMut<void(StartInfo)> done) {
@ -260,9 +275,11 @@ void ApiWrap::startExport(
using Step = StartProcess::Step; using Step = StartProcess::Step;
if (_settings->types & Settings::Type::Userpics) { if (_settings->types & Settings::Type::Userpics) {
_startProcess->steps.push_back(Step::UserpicsCount); _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); _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); _startProcess->steps.push_back(Step::LeftChannelsCount);
} }
startMainSession([=] { startMainSession([=] {
@ -361,9 +378,11 @@ void ApiWrap::finishStartProcess() {
} }
void ApiWrap::requestLeftChannelsList( void ApiWrap::requestLeftChannelsList(
Fn<bool(int count)> progress,
FnMut<void(Data::DialogsInfo&&)> done) { FnMut<void(Data::DialogsInfo&&)> done) {
Expects(_leftChannelsProcess != nullptr); Expects(_leftChannelsProcess != nullptr);
_leftChannelsProcess->progress = std::move(progress);
_leftChannelsProcess->done = std::move(done); _leftChannelsProcess->done = std::move(done);
requestLeftChannelsSlice(); requestLeftChannelsSlice();
} }
@ -382,27 +401,18 @@ void ApiWrap::requestLeftChannelsSlice() {
}); });
} }
rpl::producer<int> ApiWrap::leftChannelsLoadedCount() const { void ApiWrap::requestDialogsList(
Expects(_leftChannelsProcess != nullptr); Fn<bool(int count)> progress,
FnMut<void(Data::DialogsInfo&&)> done) {
return _leftChannelsProcess->count.value();
}
void ApiWrap::requestDialogsList(FnMut<void(Data::DialogsInfo&&)> done) {
Expects(_dialogsProcess == nullptr); Expects(_dialogsProcess == nullptr);
_dialogsProcess = std::make_unique<DialogsProcess>(); _dialogsProcess = std::make_unique<DialogsProcess>();
_dialogsProcess->progress = std::move(progress);
_dialogsProcess->done = std::move(done); _dialogsProcess->done = std::move(done);
requestDialogsSlice(); requestDialogsSlice();
} }
rpl::producer<int> ApiWrap::dialogsLoadedCount() const {
Expects(_dialogsProcess != nullptr);
return _dialogsProcess->count.value();
}
void ApiWrap::startMainSession(FnMut<void()> done) { void ApiWrap::startMainSession(FnMut<void()> done) {
const auto sizeLimit = _settings->media.sizeLimit; const auto sizeLimit = _settings->media.sizeLimit;
const auto hasFiles = (_settings->media.types != 0) && (sizeLimit > 0); const auto hasFiles = (_settings->media.types != 0) && (sizeLimit > 0);
@ -456,33 +466,35 @@ void ApiWrap::requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done) {
} }
void ApiWrap::requestUserpics( void ApiWrap::requestUserpics(
FnMut<void(Data::UserpicsInfo&&)> start, FnMut<bool(Data::UserpicsInfo&&)> start,
Fn<void(Data::UserpicsSlice&&)> slice, Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::UserpicsSlice&&)> slice,
FnMut<void()> finish) { FnMut<void()> finish) {
Expects(_userpicsProcess == nullptr); Expects(_userpicsProcess == nullptr);
_userpicsProcess = std::make_unique<UserpicsProcess>(); _userpicsProcess = std::make_unique<UserpicsProcess>();
_userpicsProcess->start = std::move(start); _userpicsProcess->start = std::move(start);
_userpicsProcess->fileProgress = std::move(progress);
_userpicsProcess->handleSlice = std::move(slice); _userpicsProcess->handleSlice = std::move(slice);
_userpicsProcess->finish = std::move(finish); _userpicsProcess->finish = std::move(finish);
mainRequest(MTPphotos_GetUserPhotos( mainRequest(MTPphotos_GetUserPhotos(
_user, _user,
MTP_int(0), // offset MTP_int(0), // offset
MTP_long(0), // max_id MTP_long(_userpicsProcess->maxId),
MTP_int(kUserpicsSliceLimit) MTP_int(kUserpicsSliceLimit)
)).done([=](const MTPphotos_Photos &result) mutable { )).done([=](const MTPphotos_Photos &result) mutable {
Expects(_userpicsProcess != nullptr); Expects(_userpicsProcess != nullptr);
_userpicsProcess->start([&] { auto startInfo = result.match(
auto info = Data::UserpicsInfo(); [](const MTPDphotos_photos &data) {
result.match([&](const MTPDphotos_photos &data) { return Data::UserpicsInfo{ data.vphotos.v.size() };
info.count = data.vphotos.v.size(); }, [](const MTPDphotos_photosSlice &data) {
}, [&](const MTPDphotos_photosSlice &data) { return Data::UserpicsInfo{ data.vcount.v };
info.count = data.vcount.v; });
}); if (!_userpicsProcess->start(std::move(startInfo))) {
return info; return;
}()); }
handleUserpicsSlice(result); handleUserpicsSlice(result);
}).send(); }).send();
@ -523,34 +535,54 @@ void ApiWrap::loadNextUserpic() {
} }
const auto ready = processFileLoad( const auto ready = processFileLoad(
list[index].image.file, list[index].image.file,
[=](FileProgress value) { return loadUserpicProgress(value); },
[=](const QString &path) { loadUserpicDone(path); }); [=](const QString &path) { loadUserpicDone(path); });
if (!ready) { if (!ready) {
return; return;
} }
} }
const auto lastUserpicId = list.empty() finishUserpicsSlice();
? base::none }
: base::make_optional(list.back().id);
if (!list.empty()) { void ApiWrap::finishUserpicsSlice() {
_userpicsProcess->handleSlice(*base::take(_userpicsProcess->slice)); 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) { if (_userpicsProcess->lastSlice) {
finishUserpics(); finishUserpics();
return; return;
} }
Assert(lastUserpicId.has_value());
mainRequest(MTPphotos_GetUserPhotos( mainRequest(MTPphotos_GetUserPhotos(
_user, _user,
MTP_int(0), MTP_int(0), // offset
MTP_long(*lastUserpicId), MTP_long(_userpicsProcess->maxId),
MTP_int(kUserpicsSliceLimit) MTP_int(kUserpicsSliceLimit)
)).done([=](const MTPphotos_Photos &result) { )).done([=](const MTPphotos_Photos &result) {
handleUserpicsSlice(result); handleUserpicsSlice(result);
}).send(); }).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) { void ApiWrap::loadUserpicDone(const QString &relativePath) {
Expects(_userpicsProcess != nullptr); Expects(_userpicsProcess != nullptr);
Expects(_userpicsProcess->slice.has_value()); Expects(_userpicsProcess->slice.has_value());
@ -588,16 +620,25 @@ void ApiWrap::requestSessions(FnMut<void(Data::SessionsList&&)> done) {
void ApiWrap::requestMessages( void ApiWrap::requestMessages(
const Data::DialogInfo &info, 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) { FnMut<void()> done) {
Expects(_chatProcess == nullptr); Expects(_chatProcess == nullptr);
_chatProcess = std::make_unique<ChatProcess>(); _chatProcess = std::make_unique<ChatProcess>();
_chatProcess->info = info; _chatProcess->info = info;
_chatProcess->start = std::move(start);
_chatProcess->fileProgress = std::move(progress);
_chatProcess->handleSlice = std::move(slice); _chatProcess->handleSlice = std::move(slice);
_chatProcess->done = std::move(done); _chatProcess->done = std::move(done);
requestMessagesSlice(); requestMessagesSlice([=](int count) {
Expects(_chatProcess != nullptr);
_chatProcess->info.messagesCount = count;
return _chatProcess->start(_chatProcess->info);
});
} }
void ApiWrap::requestDialogsSlice() { void ApiWrap::requestDialogsSlice() {
@ -628,6 +669,11 @@ void ApiWrap::requestDialogsSlice() {
appendDialogsSlice(std::move(info)); appendDialogsSlice(std::move(info));
const auto count = _dialogsProcess->info.list.size();
if (!_dialogsProcess->progress(count)) {
return;
}
requestDialogsSlice(); requestDialogsSlice();
} }
}).send(); }).send();
@ -654,7 +700,7 @@ void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
Expects(_leftChannelsProcess != nullptr); Expects(_leftChannelsProcess != nullptr);
mainRequest(MTPchannels_GetLeftChannels( mainRequest(MTPchannels_GetLeftChannels(
MTP_int(_leftChannelsProcess->info.list.size()) MTP_int(_leftChannelsProcess->offset)
)).done([=, done = std::move(done)]( )).done([=, done = std::move(done)](
const MTPmessages_Chats &result) mutable { const MTPmessages_Chats &result) mutable {
Expects(_leftChannelsProcess != nullptr); Expects(_leftChannelsProcess != nullptr);
@ -662,6 +708,11 @@ void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
appendLeftChannelsSlice(Data::ParseLeftChannelsInfo(result)); appendLeftChannelsSlice(Data::ParseLeftChannelsInfo(result));
const auto process = _leftChannelsProcess.get(); const auto process = _leftChannelsProcess.get();
process->offset += result.match(
[](const auto &data) {
return int(data.vchats.v.size());
});
process->fullCount = result.match( process->fullCount = result.match(
[](const MTPDmessages_chats &data) { [](const MTPDmessages_chats &data) {
return int(data.vchats.v.size()); return int(data.vchats.v.size());
@ -676,7 +727,11 @@ void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
return data.vchats.v.isEmpty(); return data.vchats.v.isEmpty();
}); });
process->count = process->info.list.size(); if (process->progress) {
if (!process->progress(process->info.list.size())) {
return;
}
}
done(); done();
}).send(); }).send();
@ -710,23 +765,30 @@ void ApiWrap::appendChatsSlice(
} }
} }
void ApiWrap::requestMessagesSlice() { void ApiWrap::requestMessagesSlice(FnMut<bool(int count)> start) {
Expects(_chatProcess != nullptr); Expects(_chatProcess != nullptr);
// #TODO export auto handleResult = [=, start = std::move(start)](
if (_chatProcess->info.input.match([](const MTPDinputPeerUser &value) { const MTPmessages_Messages &result) mutable {
return !value.vaccess_hash.v;
}, [](const auto &data) { return false; })) {
finishMessages();
return;
}
auto handleResult = [=](const MTPmessages_Messages &result) mutable {
Expects(_chatProcess != nullptr); 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) { result.match([&](const MTPDmessages_messagesNotModified &data) {
error("Unexpected messagesNotModified received."); error("Unexpected messagesNotModified received.");
}, [&](const auto &data) { }, [&](const auto &data) {
if (start && !start(count)) {
return;
}
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) { if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
_chatProcess->lastSlice = true; _chatProcess->lastSlice = true;
} }
@ -790,8 +852,12 @@ void ApiWrap::loadNextMessageFile() {
if (index >= list.size()) { if (index >= list.size()) {
break; break;
} }
const auto fileProgress = [=](FileProgress value) {
return loadMessageFileProgress(value);
};
const auto ready = processFileLoad( const auto ready = processFileLoad(
list[index].file(), list[index].file(),
fileProgress,
[=](const QString &path) { loadMessageFileDone(path); }, [=](const QString &path) { loadMessageFileDone(path); },
&list[index]); &list[index]);
if (!ready) { if (!ready) {
@ -808,7 +874,9 @@ void ApiWrap::finishMessagesSlice() {
auto slice = *base::take(_chatProcess->slice); auto slice = *base::take(_chatProcess->slice);
if (!slice.list.empty()) { if (!slice.list.empty()) {
_chatProcess->offsetId = slice.list.back().id + 1; _chatProcess->offsetId = slice.list.back().id + 1;
_chatProcess->handleSlice(std::move(slice)); if (!_chatProcess->handleSlice(std::move(slice))) {
return;
}
} }
if (_chatProcess->lastSlice) { if (_chatProcess->lastSlice) {
finishMessages(); 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) { void ApiWrap::loadMessageFileDone(const QString &relativePath) {
Expects(_chatProcess != nullptr); Expects(_chatProcess != nullptr);
Expects(_chatProcess->slice.has_value()); Expects(_chatProcess->slice.has_value());
@ -838,6 +918,7 @@ void ApiWrap::finishMessages() {
bool ApiWrap::processFileLoad( bool ApiWrap::processFileLoad(
Data::File &file, Data::File &file,
Fn<bool(FileProgress)> progress,
FnMut<void(QString)> done, FnMut<void(QString)> done,
Data::Message *message) { Data::Message *message) {
using SkipReason = Data::File::SkipReason; using SkipReason = Data::File::SkipReason;
@ -848,7 +929,7 @@ bool ApiWrap::processFileLoad(
file.skipReason = SkipReason::Unavailable; file.skipReason = SkipReason::Unavailable;
return true; return true;
} else if (writePreloadedFile(file)) { } else if (writePreloadedFile(file)) {
return true; return !file.relativePath.isEmpty();
} }
using Type = MediaSettings::Type; using Type = MediaSettings::Type;
@ -878,7 +959,7 @@ bool ApiWrap::processFileLoad(
file.skipReason = SkipReason::FileSize; file.skipReason = SkipReason::FileSize;
return true; return true;
} }
loadFile(file, std::move(done)); loadFile(file, std::move(progress), std::move(done));
return false; return false;
} }
@ -893,25 +974,39 @@ bool ApiWrap::writePreloadedFile(Data::File &file) {
} else if (!file.content.isEmpty()) { } else if (!file.content.isEmpty()) {
const auto process = prepareFileProcess(file); const auto process = prepareFileProcess(file);
auto &output = _fileProcess->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; file.relativePath = process->relativePath;
_fileCache->save(file.location, file.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; return false;
} }
void ApiWrap::loadFile( void ApiWrap::loadFile(
const Data::File &file, const Data::File &file,
Fn<bool(FileProgress)> progress,
FnMut<void(QString)> done) { FnMut<void(QString)> done) {
Expects(_fileProcess == nullptr); Expects(_fileProcess == nullptr);
Expects(file.location.dcId != 0); Expects(file.location.dcId != 0);
_fileProcess = prepareFileProcess(file); _fileProcess = prepareFileProcess(file);
_fileProcess->progress = std::move(progress);
_fileProcess->done = std::move(done); _fileProcess->done = std::move(done);
if (_fileProcess->progress) {
const auto progress = FileProgress{
_fileProcess->file.size(),
_fileProcess->size
};
if (!_fileProcess->progress(progress)) {
return;
}
}
loadFilePart(); loadFilePart();
} }
@ -989,14 +1084,19 @@ void ApiWrap::filePartDone(int offset, const MTPupload_File &result) {
auto &file = _fileProcess->file; auto &file = _fileProcess->file;
while (!requests.empty() && !requests.front().bytes.isEmpty()) { while (!requests.empty() && !requests.front().bytes.isEmpty()) {
const auto &bytes = requests.front().bytes; const auto &bytes = requests.front().bytes;
if (file.writeBlock(bytes) != Output::File::Result::Success) { if (const auto result = file.writeBlock(bytes); !result) {
error(QString("Could not write bytes to '%1'." ioError(result);
).arg(_fileProcess->relativePath));
return; return;
} }
requests.pop_front(); requests.pop_front();
} }
if (_fileProcess->progress) {
_fileProcess->progress(FileProgress{
file.size(),
_fileProcess->size });
}
if (!requests.empty() if (!requests.empty()
|| !_fileProcess->size || !_fileProcess->size
|| _fileProcess->size > _fileProcess->offset) { || _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))); 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; ApiWrap::~ApiWrap() = default;
} // namespace Export } // namespace Export

View File

@ -25,6 +25,10 @@ struct MessagesSlice;
struct Message; struct Message;
} // namespace Data } // namespace Data
namespace Output {
struct Result;
} // namespace Output
struct Settings; struct Settings;
class ApiWrap { class ApiWrap {
@ -32,6 +36,7 @@ public:
ApiWrap(Fn<void(FnMut<void()>)> runner); ApiWrap(Fn<void(FnMut<void()>)> runner);
rpl::producer<RPCError> errors() const; rpl::producer<RPCError> errors() const;
rpl::producer<Output::Result> ioErrors() const;
struct StartInfo { struct StartInfo {
int userpicsCount = 0; int userpicsCount = 0;
@ -42,17 +47,24 @@ public:
const Settings &settings, const Settings &settings,
FnMut<void(StartInfo)> done); FnMut<void(StartInfo)> done);
void requestLeftChannelsList(FnMut<void(Data::DialogsInfo&&)> done); void requestLeftChannelsList(
rpl::producer<int> leftChannelsLoadedCount() const; Fn<bool(int count)> progress,
FnMut<void(Data::DialogsInfo&&)> done);
void requestDialogsList(FnMut<void(Data::DialogsInfo&&)> done); void requestDialogsList(
rpl::producer<int> dialogsLoadedCount() const; Fn<bool(int count)> progress,
FnMut<void(Data::DialogsInfo&&)> done);
void requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done); void requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done);
struct DownloadProgress {
int itemIndex = 0;
int ready = 0;
int total = 0;
};
void requestUserpics( void requestUserpics(
FnMut<void(Data::UserpicsInfo&&)> start, FnMut<bool(Data::UserpicsInfo&&)> start,
Fn<void(Data::UserpicsSlice&&)> slice, Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::UserpicsSlice&&)> slice,
FnMut<void()> finish); FnMut<void()> finish);
void requestContacts(FnMut<void(Data::ContactsList&&)> done); void requestContacts(FnMut<void(Data::ContactsList&&)> done);
@ -61,7 +73,9 @@ public:
void requestMessages( void requestMessages(
const Data::DialogInfo &info, 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); FnMut<void()> done);
~ApiWrap(); ~ApiWrap();
@ -71,6 +85,7 @@ private:
struct StartProcess; struct StartProcess;
struct UserpicsProcess; struct UserpicsProcess;
struct FileProcess; struct FileProcess;
struct FileProgress;
struct LeftChannelsProcess; struct LeftChannelsProcess;
struct DialogsProcess; struct DialogsProcess;
struct ChatProcess; struct ChatProcess;
@ -85,7 +100,9 @@ private:
void handleUserpicsSlice(const MTPphotos_Photos &result); void handleUserpicsSlice(const MTPphotos_Photos &result);
void loadUserpicsFiles(Data::UserpicsSlice &&slice); void loadUserpicsFiles(Data::UserpicsSlice &&slice);
void loadNextUserpic(); void loadNextUserpic();
bool loadUserpicProgress(FileProgress value);
void loadUserpicDone(const QString &relativePath); void loadUserpicDone(const QString &relativePath);
void finishUserpicsSlice();
void finishUserpics(); void finishUserpics();
void requestDialogsSlice(); void requestDialogsSlice();
@ -98,15 +115,17 @@ private:
void appendChatsSlice(Data::DialogsInfo &to, Data::DialogsInfo &&info); void appendChatsSlice(Data::DialogsInfo &to, Data::DialogsInfo &&info);
void requestMessagesSlice(); void requestMessagesSlice(FnMut<bool(int count)> start = nullptr);
void loadMessagesFiles(Data::MessagesSlice &&slice); void loadMessagesFiles(Data::MessagesSlice &&slice);
void loadNextMessageFile(); void loadNextMessageFile();
bool loadMessageFileProgress(FileProgress value);
void loadMessageFileDone(const QString &relativePath); void loadMessageFileDone(const QString &relativePath);
void finishMessagesSlice(); void finishMessagesSlice();
void finishMessages(); void finishMessages();
bool processFileLoad( bool processFileLoad(
Data::File &file, Data::File &file,
Fn<bool(FileProgress)> progress,
FnMut<void(QString)> done, FnMut<void(QString)> done,
Data::Message *message = nullptr); Data::Message *message = nullptr);
std::unique_ptr<FileProcess> prepareFileProcess( std::unique_ptr<FileProcess> prepareFileProcess(
@ -114,6 +133,7 @@ private:
bool writePreloadedFile(Data::File &file); bool writePreloadedFile(Data::File &file);
void loadFile( void loadFile(
const Data::File &file, const Data::File &file,
Fn<bool(FileProgress)> progress,
FnMut<void(QString)> done); FnMut<void(QString)> done);
void loadFilePart(); void loadFilePart();
void filePartDone(int offset, const MTPupload_File &result); void filePartDone(int offset, const MTPupload_File &result);
@ -127,6 +147,7 @@ private:
void error(RPCError &&error); void error(RPCError &&error);
void error(const QString &text); void error(const QString &text);
void ioError(const Output::Result &result);
MTP::ConcurrentSender _mtp; MTP::ConcurrentSender _mtp;
base::optional<uint64> _takeoutId; base::optional<uint64> _takeoutId;
@ -143,6 +164,7 @@ private:
std::unique_ptr<ChatProcess> _chatProcess; std::unique_ptr<ChatProcess> _chatProcess;
rpl::event_stream<RPCError> _errors; rpl::event_stream<RPCError> _errors;
rpl::event_stream<Output::Result> _ioErrors;
}; };

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/export_settings.h" #include "export/export_settings.h"
#include "export/data/export_data_types.h" #include "export/data/export_data_types.h"
#include "export/output/export_output_abstract.h" #include "export/output/export_output_abstract.h"
#include "export/output/export_output_result.h"
namespace Export { namespace Export {
@ -34,9 +35,11 @@ public:
private: private:
using Step = ProcessingState::Step; using Step = ProcessingState::Step;
using DownloadProgress = ApiWrap::DownloadProgress;
void setState(State &&state); void setState(State &&state);
void ioError(const QString &path); void ioError(const QString &path);
bool ioCatchError(Output::Result result);
void setFinishedState(); void setFinishedState();
//void requestPasswordState(); //void requestPasswordState();
@ -65,11 +68,11 @@ private:
ProcessingState stateLeftChannelsList(int processed) const; ProcessingState stateLeftChannelsList(int processed) const;
ProcessingState stateDialogsList(int processed) const; ProcessingState stateDialogsList(int processed) const;
ProcessingState statePersonalInfo() const; ProcessingState statePersonalInfo() const;
ProcessingState stateUserpics() const; ProcessingState stateUserpics(DownloadProgress progress) const;
ProcessingState stateContacts() const; ProcessingState stateContacts() const;
ProcessingState stateSessions() const; ProcessingState stateSessions() const;
ProcessingState stateLeftChannels() const; ProcessingState stateLeftChannels(DownloadProgress progress) const;
ProcessingState stateDialogs() const; ProcessingState stateDialogs(DownloadProgress progress) const;
int substepsInStep(Step step) const; int substepsInStep(Step step) const;
@ -77,15 +80,23 @@ private:
ApiWrap _api; ApiWrap _api;
Settings _settings; Settings _settings;
Data::DialogsInfo _leftChannelsInfo; Data::DialogsInfo _leftChannelsInfo;
Data::DialogsInfo _dialogsInfo;
int _leftChannelIndex = -1; int _leftChannelIndex = -1;
Data::DialogsInfo _dialogsInfo;
int _dialogIndex = -1; int _dialogIndex = -1;
int _messagesWritten = 0;
int _messagesCount = 0;
int _userpicsWritten = 0;
int _userpicsCount = 0;
// rpl::variable<State> fails to compile in MSVC :( // rpl::variable<State> fails to compile in MSVC :(
State _state; State _state;
rpl::event_stream<State> _stateChanges; rpl::event_stream<State> _stateChanges;
std::vector<int> _substepsInStep; std::shared_ptr<const std::vector<int>> _substepsInStep;
std::unique_ptr<Output::AbstractWriter> _writer; std::unique_ptr<Output::AbstractWriter> _writer;
std::vector<Step> _steps; std::vector<Step> _steps;
@ -100,7 +111,12 @@ Controller::Controller(crl::weak_on_queue<Controller> weak)
, _state(PasswordCheckState{}) { , _state(PasswordCheckState{}) {
_api.errors( _api.errors(
) | rpl::start_with_next([=](RPCError &&error) { ) | 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); }, _lifetime);
//requestPasswordState(); //requestPasswordState();
@ -127,7 +143,15 @@ void Controller::setState(State &&state) {
} }
void Controller::ioError(const QString &path) { 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) { //void Controller::submitPassword(const QString &password) {
@ -223,7 +247,7 @@ void Controller::fillExportSteps() {
using Type = Settings::Type; using Type = Settings::Type;
_steps.push_back(Step::Initializing); _steps.push_back(Step::Initializing);
if (_settings.types & Type::GroupsChannelsMask) { if (_settings.types & Type::GroupsChannelsMask) {
_steps.push_back(Step::LeftChannels); _steps.push_back(Step::LeftChannelsList);
} }
if (_settings.types & Type::AnyChatsMask) { if (_settings.types & Type::AnyChatsMask) {
_steps.push_back(Step::DialogsList); _steps.push_back(Step::DialogsList);
@ -243,15 +267,19 @@ void Controller::fillExportSteps() {
if (_settings.types & Type::AnyChatsMask) { if (_settings.types & Type::AnyChatsMask) {
_steps.push_back(Step::Dialogs); _steps.push_back(Step::Dialogs);
} }
if (_settings.types & Type::GroupsChannelsMask) {
_steps.push_back(Step::LeftChannels);
}
} }
void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) { void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
auto result = std::vector<int>();
const auto push = [&](Step step, int count) { const auto push = [&](Step step, int count) {
const auto index = static_cast<int>(step); const auto index = static_cast<int>(step);
if (index >= _substepsInStep.size()) { if (index >= result.size()) {
_substepsInStep.resize(index + 1, 0); result.resize(index + 1, 0);
} }
_substepsInStep[index] = count; result[index] = count;
}; };
push(Step::Initializing, 1); push(Step::Initializing, 1);
if (_settings.types & Settings::Type::GroupsChannelsMask) { if (_settings.types & Settings::Type::GroupsChannelsMask) {
@ -278,14 +306,20 @@ void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
if (_settings.types & Settings::Type::AnyChatsMask) { if (_settings.types & Settings::Type::AnyChatsMask) {
push(Step::Dialogs, info.dialogsCount); push(Step::Dialogs, info.dialogsCount);
} }
_substepsInStep = std::make_shared<const std::vector<int>>(
std::move(result));
} }
void Controller::exportNext() { void Controller::exportNext() {
if (!++_stepIndex) { if (!++_stepIndex) {
_writer->start(_settings); if (ioCatchError(_writer->start(_settings))) {
return;
}
} }
if (_stepIndex >= _steps.size()) { if (_stepIndex >= _steps.size()) {
_writer->finish(); if (ioCatchError(_writer->finish())) {
return;
}
setFinishedState(); setFinishedState();
return; return;
} }
@ -314,63 +348,82 @@ void Controller::initialize() {
} }
void Controller::collectLeftChannels() { void Controller::collectLeftChannels() {
_api.requestLeftChannelsList([=](Data::DialogsInfo &&result) { _api.requestLeftChannelsList([=](int count) {
setState(stateLeftChannelsList(count));
return true;
}, [=](Data::DialogsInfo &&result) {
_leftChannelsInfo = std::move(result); _leftChannelsInfo = std::move(result);
exportNext(); exportNext();
}); });
_api.leftChannelsLoadedCount(
) | rpl::start_with_next([=](int count) {
setState(stateLeftChannelsList(count));
}, _lifetime);
} }
void Controller::collectDialogsList() { void Controller::collectDialogsList() {
_api.requestDialogsList([=](Data::DialogsInfo &&result) { _api.requestDialogsList([=](int count) {
setState(stateDialogsList(count));
return true;
}, [=](Data::DialogsInfo &&result) {
_dialogsInfo = std::move(result); _dialogsInfo = std::move(result);
exportNext(); exportNext();
}); });
_api.dialogsLoadedCount(
) | rpl::start_with_next([=](int count) {
setState(stateDialogsList(count));
}, _lifetime);
} }
void Controller::exportPersonalInfo() { void Controller::exportPersonalInfo() {
_api.requestPersonalInfo([=](Data::PersonalInfo &&result) { _api.requestPersonalInfo([=](Data::PersonalInfo &&result) {
_writer->writePersonal(result); if (ioCatchError(_writer->writePersonal(result))) {
return;
}
exportNext(); exportNext();
}); });
} }
void Controller::exportUserpics() { void Controller::exportUserpics() {
_api.requestUserpics([=](Data::UserpicsInfo &&start) { _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) { }, [=](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(); exportNext();
}); });
} }
void Controller::exportContacts() { void Controller::exportContacts() {
_api.requestContacts([=](Data::ContactsList &&result) { _api.requestContacts([=](Data::ContactsList &&result) {
_writer->writeContactsList(result); if (ioCatchError(_writer->writeContactsList(result))) {
return;
}
exportNext(); exportNext();
}); });
} }
void Controller::exportSessions() { void Controller::exportSessions() {
_api.requestSessions([=](Data::SessionsList &&result) { _api.requestSessions([=](Data::SessionsList &&result) {
_writer->writeSessionsList(result); if (ioCatchError(_writer->writeSessionsList(result))) {
return;
}
exportNext(); exportNext();
}); });
} }
void Controller::exportDialogs() { void Controller::exportDialogs() {
_writer->writeDialogsStart(_dialogsInfo); if (ioCatchError(_writer->writeDialogsStart(_dialogsInfo))) {
return;
}
exportNextDialog(); exportNextDialog();
} }
@ -379,22 +432,42 @@ void Controller::exportNextDialog() {
const auto index = ++_dialogIndex; const auto index = ++_dialogIndex;
if (index < _dialogsInfo.list.size()) { if (index < _dialogsInfo.list.size()) {
const auto &info = _dialogsInfo.list[index]; const auto &info = _dialogsInfo.list[index];
_writer->writeDialogStart(info); _api.requestMessages(info, [=](const Data::DialogInfo &info) {
if (ioCatchError(_writer->writeDialogStart(info))) {
_api.requestMessages(info, [=](Data::MessagesSlice &&result) { return false;
_writer->writeDialogSlice(result); }
_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(); exportNextDialog();
}); });
return; return;
} }
_writer->writeDialogsEnd(); if (ioCatchError(_writer->writeDialogsEnd())) {
return;
}
exportNext(); exportNext();
} }
void Controller::exportLeftChannels() { void Controller::exportLeftChannels() {
_writer->writeLeftChannelsStart(_leftChannelsInfo); if (ioCatchError(_writer->writeLeftChannelsStart(_leftChannelsInfo))) {
return;
}
exportNextLeftChannel(); exportNextLeftChannel();
} }
@ -402,18 +475,36 @@ void Controller::exportLeftChannels() {
void Controller::exportNextLeftChannel() { void Controller::exportNextLeftChannel() {
const auto index = ++_leftChannelIndex; const auto index = ++_leftChannelIndex;
if (index < _leftChannelsInfo.list.size()) { if (index < _leftChannelsInfo.list.size()) {
const auto &chat = _leftChannelsInfo.list[index]; const auto &info = _leftChannelsInfo.list[index];
_writer->writeLeftChannelStart(chat); _api.requestMessages(info, [=](const Data::DialogInfo &info) {
if (ioCatchError(_writer->writeLeftChannelStart(info))) {
_api.requestMessages(chat, [=](Data::MessagesSlice &&result) { return false;
_writer->writeLeftChannelSlice(result); }
_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(); exportNextLeftChannel();
}); });
return; return;
} }
_writer->writeLeftChannelsEnd(); if (ioCatchError(_writer->writeLeftChannelsEnd())) {
return;
}
exportNext(); exportNext();
} }
@ -451,9 +542,12 @@ ProcessingState Controller::statePersonalInfo() const {
return prepareState(Step::PersonalInfo); return prepareState(Step::PersonalInfo);
} }
ProcessingState Controller::stateUserpics() const { ProcessingState Controller::stateUserpics(DownloadProgress progress) const {
return prepareState(Step::Userpics, [&](ProcessingState &result) { 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); return prepareState(Step::Sessions);
} }
ProcessingState Controller::stateLeftChannels() const { ProcessingState Controller::stateLeftChannels(
DownloadProgress progress) const {
const auto step = Step::LeftChannels; const auto step = Step::LeftChannels;
return prepareState(step, [&](ProcessingState &result) { return prepareState(step, [&](ProcessingState &result) {
//result.entityIndex = processed; result.entityIndex = _leftChannelIndex;
//result.entityCount = std::max(processed, substepsInStep(step)); 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; const auto step = Step::Dialogs;
return prepareState(step, [&](ProcessingState &result) { return prepareState(step, [&](ProcessingState &result) {
//result.entityIndex = processed; result.entityIndex = _dialogIndex;
//result.entityCount = std::max(processed, substepsInStep(step)); 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 { 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() { void Controller::setFinishedState() {

View File

@ -50,7 +50,7 @@ struct ProcessingState {
Step step = Step::Initializing; Step step = Step::Initializing;
std::vector<int> substepsInStep; std::shared_ptr<const std::vector<int>> substepsInStep;
int entityIndex = 0; int entityIndex = 0;
int entityCount = 1; int entityCount = 1;
@ -68,15 +68,13 @@ struct ProcessingState {
}; };
struct ErrorState { struct ApiErrorState {
enum class Type { RPCError data;
Unknown,
API, };
IO,
}; struct OutputErrorState {
Type type = Type::Unknown; QString path;
base::optional<RPCError> apiError;
base::optional<QString> ioErrorPath;
}; };
@ -88,7 +86,8 @@ struct FinishedState {
using State = base::optional_variant< using State = base::optional_variant<
PasswordCheckState, PasswordCheckState,
ProcessingState, ProcessingState,
ErrorState, ApiErrorState,
OutputErrorState,
FinishedState>; FinishedState>;
//struct PasswordUpdate { //struct PasswordUpdate {

View File

@ -25,41 +25,55 @@ struct Settings;
namespace Output { namespace Output {
struct Result;
enum class Format { enum class Format {
Text, Text,
Yaml,
Html, Html,
Json, Json,
}; };
class AbstractWriter { class AbstractWriter {
public: 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; [[nodiscard]] virtual Result writeUserpicsStart(
virtual bool writeUserpicsSlice(const Data::UserpicsSlice &data) = 0; const Data::UserpicsInfo &data) = 0;
virtual bool writeUserpicsEnd() = 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; [[nodiscard]] virtual Result writeDialogsStart(
virtual bool writeDialogStart(const Data::DialogInfo &data) = 0; const Data::DialogsInfo &data) = 0;
virtual bool writeDialogSlice(const Data::MessagesSlice &data) = 0; [[nodiscard]] virtual Result writeDialogStart(
virtual bool writeDialogEnd() = 0; const Data::DialogInfo &data) = 0;
virtual bool writeDialogsEnd() = 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; [[nodiscard]] virtual Result writeLeftChannelsStart(
virtual bool writeLeftChannelStart(const Data::DialogInfo &data) = 0; const Data::DialogsInfo &data) = 0;
virtual bool writeLeftChannelSlice(const Data::MessagesSlice &data) = 0; [[nodiscard]] virtual Result writeLeftChannelStart(
virtual bool writeLeftChannelEnd() = 0; const Data::DialogInfo &data) = 0;
virtual bool writeLeftChannelsEnd() = 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; virtual ~AbstractWriter() = default;

View File

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "export/output/export_output_file.h" #include "export/output/export_output_file.h"
#include "export/output/export_output_result.h"
#include <QtCore/QFileInfo> #include <QtCore/QFileInfo>
#include <QtCore/QDir> #include <QtCore/QDir>
@ -26,47 +28,57 @@ bool File::empty() const {
return !_offset; return !_offset;
} }
File::Result File::writeBlock(const QByteArray &block) { Result File::writeBlock(const QByteArray &block) {
const auto result = writeBlockAttempt(block); const auto result = writeBlockAttempt(block);
if (result != Result::Success) { if (!result) {
_file.clear(); _file.clear();
} }
return result; return result;
} }
File::Result File::writeBlockAttempt(const QByteArray &block) { Result File::writeBlockAttempt(const QByteArray &block) {
if (const auto result = reopen(); result != Result::Success) { if (const auto result = reopen(); !result) {
return result; return result;
} }
return (_file->write(block) == block.size() && _file->flush()) if (_file->write(block) == block.size() && _file->flush()) {
? Result::Success _offset += block.size();
: Result::Error; return Result::Success();
}
return error();
} }
File::Result File::reopen() { Result File::reopen() {
if (_file && _file->isOpen()) { if (_file && _file->isOpen()) {
return Result::Success; return Result::Success();
} }
_file.emplace(_path); _file.emplace(_path);
if (_file->exists()) { if (_file->exists()) {
if (_file->size() < _offset) { if (_file->size() < _offset) {
return Result::FatalError; return fatalError();
} else if (!_file->resize(_offset)) { } else if (!_file->resize(_offset)) {
return Result::Error; return error();
} }
} else if (_offset > 0) { } else if (_offset > 0) {
return Result::FatalError; return fatalError();
} }
if (_file->open(QIODevice::Append)) { if (_file->open(QIODevice::Append)) {
return Result::Success; return Result::Success();
} }
const auto info = QFileInfo(_path); const auto info = QFileInfo(_path);
const auto dir = info.absoluteDir(); const auto dir = info.absoluteDir();
return (!dir.exists() return (!dir.exists()
&& dir.mkpath(dir.absolutePath()) && dir.mkpath(dir.absolutePath())
&& _file->open(QIODevice::Append)) && _file->open(QIODevice::Append))
? Result::Success ? Result::Success()
: Result::Error; : error();
}
Result File::error() const {
return Result(Result::Type::Error, _path);
}
Result File::fatalError() const {
return Result(Result::Type::FatalError, _path);
} }
QString File::PrepareRelativePath( QString File::PrepareRelativePath(

View File

@ -16,27 +16,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export { namespace Export {
namespace Output { namespace Output {
struct Result;
class File { class File {
public: public:
File(const QString &path); File(const QString &path);
int size() const; [[nodiscard]] int size() const;
bool empty() const; [[nodiscard]] bool empty() const;
enum class Result { [[nodiscard]] Result writeBlock(const QByteArray &block);
Success,
Error,
FatalError,
};
Result writeBlock(const QByteArray &block);
static QString PrepareRelativePath( [[nodiscard]] static QString PrepareRelativePath(
const QString &folder, const QString &folder,
const QString &suggested); const QString &suggested);
private: private:
Result reopen(); [[nodiscard]] Result reopen();
Result writeBlockAttempt(const QByteArray &block); [[nodiscard]] Result writeBlockAttempt(const QByteArray &block);
[[nodiscard]] Result error() const;
[[nodiscard]] Result fatalError() const;
QString _path; QString _path;
int _offset = 0; int _offset = 0;

View 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

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "export/output/export_output_text.h" #include "export/output/export_output_text.h"
#include "export/output/export_output_result.h"
#include "export/data/export_data_types.h" #include "export/data/export_data_types.h"
#include "core/utils.h" #include "core/utils.h"
@ -431,16 +432,16 @@ QByteArray SerializeMessage(
} // namespace } // namespace
bool TextWriter::start(const Settings &settings) { Result TextWriter::start(const Settings &settings) {
Expects(settings.path.endsWith('/')); Expects(settings.path.endsWith('/'));
_settings = base::duplicate(settings); _settings = base::duplicate(settings);
_result = fileWithRelativePath(mainFileRelativePath()); _summary = fileWithRelativePath(mainFileRelativePath());
return true; return Result::Success();
} }
bool TextWriter::writePersonal(const Data::PersonalInfo &data) { Result TextWriter::writePersonal(const Data::PersonalInfo &data) {
Expects(_result != nullptr); Expects(_summary != nullptr);
const auto &info = data.user.info; const auto &info = data.user.info;
const auto serialized = "Personal information" const auto serialized = "Personal information"
@ -454,25 +455,25 @@ bool TextWriter::writePersonal(const Data::PersonalInfo &data) {
{ "Bio", data.bio }, { "Bio", data.bio },
}) })
+ kLineBreak; + kLineBreak;
return _result->writeBlock(serialized) == File::Result::Success; return _summary->writeBlock(serialized);
} }
bool TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) { Result TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
Expects(_result != nullptr); Expects(_summary != nullptr);
_userpicsCount = data.count; _userpicsCount = data.count;
if (!_userpicsCount) { if (!_userpicsCount) {
return true; return Result::Success();
} }
const auto serialized = "Personal photos " const auto serialized = "Personal photos "
"(" + Data::NumberToString(_userpicsCount) + ")" "(" + Data::NumberToString(_userpicsCount) + ")"
+ kLineBreak + kLineBreak
+ kLineBreak; + kLineBreak;
return _result->writeBlock(serialized) == File::Result::Success; return _summary->writeBlock(serialized);
} }
bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) { Result TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
Expects(_result != nullptr); Expects(_summary != nullptr);
Expects(!data.list.empty()); Expects(!data.list.empty());
auto lines = QByteArray(); auto lines = QByteArray();
@ -489,22 +490,22 @@ bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
} }
lines.append(kLineBreak); lines.append(kLineBreak);
} }
return _result->writeBlock(lines) == File::Result::Success; return _summary->writeBlock(lines);
} }
bool TextWriter::writeUserpicsEnd() { Result TextWriter::writeUserpicsEnd() {
Expects(_result != nullptr); Expects(_summary != nullptr);
return (_userpicsCount > 0) return (_userpicsCount > 0)
? _result->writeBlock(kLineBreak) == File::Result::Success ? _summary->writeBlock(kLineBreak)
: true; : Result::Success();
} }
bool TextWriter::writeContactsList(const Data::ContactsList &data) { Result TextWriter::writeContactsList(const Data::ContactsList &data) {
Expects(_result != nullptr); Expects(_summary != nullptr);
if (data.list.empty()) { if (data.list.empty()) {
return true; return Result::Success();
} }
const auto file = fileWithRelativePath("contacts.txt"); const auto file = fileWithRelativePath("contacts.txt");
@ -529,22 +530,22 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
} }
} }
const auto full = JoinList(kLineBreak, list); const auto full = JoinList(kLineBreak, list);
if (file->writeBlock(full) != File::Result::Success) { if (const auto result = file->writeBlock(full); !result) {
return false; return result;
} }
const auto header = "Contacts " const auto header = "Contacts "
"(" + Data::NumberToString(data.list.size()) + ") - contacts.txt" "(" + Data::NumberToString(data.list.size()) + ") - contacts.txt"
+ kLineBreak + kLineBreak
+ kLineBreak; + kLineBreak;
return _result->writeBlock(header) == File::Result::Success; return _summary->writeBlock(header);
} }
bool TextWriter::writeSessionsList(const Data::SessionsList &data) { Result TextWriter::writeSessionsList(const Data::SessionsList &data) {
Expects(_result != nullptr); Expects(_summary != nullptr);
if (data.list.empty()) { if (data.list.empty()) {
return true; return Result::Success();
} }
const auto file = fileWithRelativePath("sessions.txt"); const auto file = fileWithRelativePath("sessions.txt");
@ -570,67 +571,68 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
})); }));
} }
const auto full = JoinList(kLineBreak, list); const auto full = JoinList(kLineBreak, list);
if (file->writeBlock(full) != File::Result::Success) { if (const auto result = file->writeBlock(full); !result) {
return false; return result;
} }
const auto header = "Sessions " const auto header = "Sessions "
"(" + Data::NumberToString(data.list.size()) + ") - sessions.txt" "(" + Data::NumberToString(data.list.size()) + ") - sessions.txt"
+ kLineBreak + kLineBreak
+ 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"); return writeChatsStart(data, "Chats", "chats.txt");
} }
bool TextWriter::writeDialogStart(const Data::DialogInfo &data) { Result TextWriter::writeDialogStart(const Data::DialogInfo &data) {
return writeChatStart(data); return writeChatStart(data);
} }
bool TextWriter::writeDialogSlice(const Data::MessagesSlice &data) { Result TextWriter::writeDialogSlice(const Data::MessagesSlice &data) {
return writeChatSlice(data); return writeChatSlice(data);
} }
bool TextWriter::writeDialogEnd() { Result TextWriter::writeDialogEnd() {
return writeChatEnd(); return writeChatEnd();
} }
bool TextWriter::writeDialogsEnd() { Result TextWriter::writeDialogsEnd() {
return true; 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"); 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); return writeChatStart(data);
} }
bool TextWriter::writeLeftChannelSlice(const Data::MessagesSlice &data) { Result TextWriter::writeLeftChannelSlice(const Data::MessagesSlice &data) {
return writeChatSlice(data); return writeChatSlice(data);
} }
bool TextWriter::writeLeftChannelEnd() { Result TextWriter::writeLeftChannelEnd() {
return writeChatEnd(); return writeChatEnd();
} }
bool TextWriter::writeLeftChannelsEnd() { Result TextWriter::writeLeftChannelsEnd() {
return true; return Result::Success();
} }
bool TextWriter::writeChatsStart( Result TextWriter::writeChatsStart(
const Data::DialogsInfo &data, const Data::DialogsInfo &data,
const QByteArray &listName, const QByteArray &listName,
const QString &fileName) { const QString &fileName) {
Expects(_result != nullptr); Expects(_summary != nullptr);
if (data.list.empty()) { if (data.list.empty()) {
return true; return Result::Success();
} }
_dialogIndex = 0;
_dialogsCount = data.list.size(); _dialogsCount = data.list.size();
using Type = Data::DialogInfo::Type; using Type = Data::DialogInfo::Type;
@ -676,8 +678,8 @@ bool TextWriter::writeChatsStart(
})); }));
} }
const auto full = JoinList(kLineBreak, list); const auto full = JoinList(kLineBreak, list);
if (file->writeBlock(full) != File::Result::Success) { if (const auto result = file->writeBlock(full); !result) {
return false; return result;
} }
const auto header = listName + " " const auto header = listName + " "
@ -685,10 +687,10 @@ bool TextWriter::writeChatsStart(
+ fileName.toUtf8() + fileName.toUtf8()
+ kLineBreak + kLineBreak
+ 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(_chat == nullptr);
Expects(_dialogIndex < _dialogsCount); Expects(_dialogIndex < _dialogsCount);
@ -697,10 +699,10 @@ bool TextWriter::writeChatStart(const Data::DialogInfo &data) {
_chat = fileWithRelativePath(data.relativePath + "messages.txt"); _chat = fileWithRelativePath(data.relativePath + "messages.txt");
_dialogEmpty = true; _dialogEmpty = true;
_dialogOnlyMy = data.onlyMyMessages; _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(_chat != nullptr);
Expects(!data.list.empty()); Expects(!data.list.empty());
@ -717,23 +719,26 @@ bool TextWriter::writeChatSlice(const Data::MessagesSlice &data) {
const auto full = _chat->empty() const auto full = _chat->empty()
? JoinList(kLineBreak, list) ? JoinList(kLineBreak, list)
: kLineBreak + 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); Expects(_chat != nullptr);
if (_dialogEmpty) { if (_dialogEmpty) {
_chat->writeBlock(_dialogOnlyMy const auto result = _chat->writeBlock(_dialogOnlyMy
? "No outgoing messages in this chat." ? "No outgoing messages in this chat."
: "No messages in this chat."); : "No messages in this chat.");
if (!result) {
return result;
}
} }
_chat = nullptr; _chat = nullptr;
return true; return Result::Success();
} }
bool TextWriter::finish() { Result TextWriter::finish() {
return true; return Result::Success();
} }
QString TextWriter::mainFilePath() { QString TextWriter::mainFilePath() {

View File

@ -16,31 +16,31 @@ namespace Output {
class TextWriter : public AbstractWriter { class TextWriter : public AbstractWriter {
public: 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; Result writeUserpicsStart(const Data::UserpicsInfo &data) override;
bool writeUserpicsSlice(const Data::UserpicsSlice &data) override; Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
bool writeUserpicsEnd() 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; Result writeDialogsStart(const Data::DialogsInfo &data) override;
bool writeDialogStart(const Data::DialogInfo &data) override; Result writeDialogStart(const Data::DialogInfo &data) override;
bool writeDialogSlice(const Data::MessagesSlice &data) override; Result writeDialogSlice(const Data::MessagesSlice &data) override;
bool writeDialogEnd() override; Result writeDialogEnd() override;
bool writeDialogsEnd() override; Result writeDialogsEnd() override;
bool writeLeftChannelsStart(const Data::DialogsInfo &data) override; Result writeLeftChannelsStart(const Data::DialogsInfo &data) override;
bool writeLeftChannelStart(const Data::DialogInfo &data) override; Result writeLeftChannelStart(const Data::DialogInfo &data) override;
bool writeLeftChannelSlice(const Data::MessagesSlice &data) override; Result writeLeftChannelSlice(const Data::MessagesSlice &data) override;
bool writeLeftChannelEnd() override; Result writeLeftChannelEnd() override;
bool writeLeftChannelsEnd() override; Result writeLeftChannelsEnd() override;
bool finish() override; Result finish() override;
QString mainFilePath() override; QString mainFilePath() override;
@ -49,17 +49,17 @@ private:
QString pathWithRelativePath(const QString &path) const; QString pathWithRelativePath(const QString &path) const;
std::unique_ptr<File> fileWithRelativePath(const QString &path) const; std::unique_ptr<File> fileWithRelativePath(const QString &path) const;
bool writeChatsStart( Result writeChatsStart(
const Data::DialogsInfo &data, const Data::DialogsInfo &data,
const QByteArray &listName, const QByteArray &listName,
const QString &fileName); const QString &fileName);
bool writeChatStart(const Data::DialogInfo &data); Result writeChatStart(const Data::DialogInfo &data);
bool writeChatSlice(const Data::MessagesSlice &data); Result writeChatSlice(const Data::MessagesSlice &data);
bool writeChatEnd(); Result writeChatEnd();
Settings _settings; Settings _settings;
std::unique_ptr<File> _result; std::unique_ptr<File> _summary;
int _userpicsCount = 0; int _userpicsCount = 0;
int _dialogsCount = 0; int _dialogsCount = 0;
@ -67,10 +67,6 @@ private:
bool _dialogOnlyMy = false; bool _dialogOnlyMy = false;
bool _dialogEmpty = true; bool _dialogEmpty = true;
int _leftChannelsCount = 0;
int _leftChannelIndex = 0;
bool _leftChannelEmpty = true;
std::unique_ptr<File> _chat; std::unique_ptr<File> _chat;
}; };

View File

@ -37,3 +37,8 @@ exportFileSizeLabel: LabelSimple(defaultLabelSimple) {
} }
exportFileSizePadding: margins(22px, 8px, 22px, 8px); exportFileSizePadding: margins(22px, 8px, 22px, 8px);
exportFileSizeLabelBottom: 18px; exportFileSizeLabelBottom: 18px;
exportErrorLabel: FlatLabel(boxLabel) {
minWidth: 175px;
align: align(top);
textFg: boxTextFgError;
}

View 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

View 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

View File

@ -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_panel_controller.h"
#include "export/view/export_view_settings.h" #include "export/view/export_view_settings.h"
#include "export/view/export_view_progress.h"
#include "export/view/export_view_done.h" #include "export/view/export_view_done.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/separate_panel.h" #include "ui/widgets/separate_panel.h"
#include "ui/wrap/padding_wrap.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "styles/style_export.h" #include "styles/style_export.h"
@ -43,6 +46,9 @@ void PanelController::showSettings() {
settings->startClicks( settings->startClicks(
) | rpl::start_with_next([=](const Settings &settings) { ) | rpl::start_with_next([=](const Settings &settings) {
_panel->showInner(base::make_unique_q<ProgressWidget>(
_panel.get(),
ContentFromState(_process->state())));
_process->startExport(settings); _process->startExport(settings);
}, settings->lifetime()); }, settings->lifetime());
@ -54,6 +60,34 @@ void PanelController::showSettings() {
_panel->showInner(std::move(settings)); _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 { rpl::producer<> PanelController::closed() const {
return _panelCloseEvents.events( return _panelCloseEvents.events(
) | rpl::flatten_latest( ) | rpl::flatten_latest(
@ -67,7 +101,11 @@ void PanelController::updateState(State &&state) {
createPanel(); createPanel();
} }
_state = std::move(state); _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; const auto path = finished->path;
auto done = base::make_unique_q<DoneWidget>(_panel.get()); auto done = base::make_unique_q<DoneWidget>(_panel.get());

View File

@ -31,6 +31,9 @@ private:
void createPanel(); void createPanel();
void updateState(State &&state); void updateState(State &&state);
void showSettings(); void showSettings();
void showError(const ApiErrorState &error);
void showError(const OutputErrorState &error);
void showError(const QString &text);
not_null<ControllerWrap*> _process; not_null<ControllerWrap*> _process;

View 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

View 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

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtp_instance.h" #include "mtproto/mtp_instance.h"
#include "mtproto/rpc_sender.h" #include "mtproto/rpc_sender.h"
#include "mtproto/session.h"
namespace MTP { namespace MTP {
@ -178,7 +179,13 @@ void ConcurrentSender::senderRequestDone(
mtpRequestId requestId, mtpRequestId requestId,
bytes::const_span result) { bytes::const_span result) {
if (auto handlers = _requests.take(requestId)) { 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()));
}
} }
} }

View File

@ -203,14 +203,11 @@ void ConcurrentSender::RequestBuilder::setDoneHandler(
_handlers.done = [handler = std::move(invoke)]( _handlers.done = [handler = std::move(invoke)](
mtpRequestId requestId, mtpRequestId requestId,
bytes::const_span result) mutable { bytes::const_span result) mutable {
try { auto from = reinterpret_cast<const mtpPrime*>(result.data());
auto from = reinterpret_cast<const mtpPrime*>(result.data()); const auto end = from + result.size() / sizeof(mtpPrime);
const auto end = from + result.size() / sizeof(mtpPrime); Response data;
Response data; data.read(from, end);
data.read(from, end); std::move(handler)(requestId, std::move(data));
std::move(handler)(requestId, std::move(data));
} catch (...) {
}
}; };
} }

View File

@ -225,10 +225,14 @@
<(src_loc)/dialogs/dialogs_search_from_controllers.h <(src_loc)/dialogs/dialogs_search_from_controllers.h
<(src_loc)/dialogs/dialogs_widget.cpp <(src_loc)/dialogs/dialogs_widget.cpp
<(src_loc)/dialogs/dialogs_widget.h <(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.cpp
<(src_loc)/export/view/export_view_done.h <(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.cpp
<(src_loc)/export/view/export_view_panel_controller.h <(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.cpp
<(src_loc)/export/view/export_view_settings.h <(src_loc)/export/view/export_view_settings.h
<(src_loc)/history/admin_log/history_admin_log_filter.cpp <(src_loc)/history/admin_log/history_admin_log_filter.cpp