From 5f01751660ec0b68a11582505171dc7f57282705 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 19 Jun 2018 11:42:21 +0100 Subject: [PATCH] Display errors in export UI. All errors are now fatal errors :( --- Telegram/Resources/langs/lang.strings | 13 +- .../export/data/export_data_types.cpp | 21 +- .../export/data/export_data_types.h | 3 + .../SourceFiles/export/export_api_wrap.cpp | 234 +++++++++++++----- Telegram/SourceFiles/export/export_api_wrap.h | 40 ++- .../SourceFiles/export/export_controller.cpp | 216 +++++++++++----- .../SourceFiles/export/export_controller.h | 21 +- .../export/output/export_output_abstract.h | 68 +++-- .../export/output/export_output_file.cpp | 42 ++-- .../export/output/export_output_file.h | 22 +- .../export/output/export_output_result.h | 48 ++++ .../export/output/export_output_text.cpp | 117 ++++----- .../export/output/export_output_text.h | 50 ++-- Telegram/SourceFiles/export/view/export.style | 5 + .../export/view/export_view_content.cpp | 53 ++++ .../export/view/export_view_content.h | 41 +++ .../view/export_view_panel_controller.cpp | 40 ++- .../view/export_view_panel_controller.h | 3 + .../export/view/export_view_progress.cpp | 46 ++++ .../export/view/export_view_progress.h | 35 +++ .../SourceFiles/mtproto/concurrent_sender.cpp | 9 +- .../SourceFiles/mtproto/concurrent_sender.h | 13 +- Telegram/gyp/telegram_sources.txt | 4 + 23 files changed, 852 insertions(+), 292 deletions(-) create mode 100644 Telegram/SourceFiles/export/output/export_output_result.h create mode 100644 Telegram/SourceFiles/export/view/export_view_content.cpp create mode 100644 Telegram/SourceFiles/export/view/export_view_content.h create mode 100644 Telegram/SourceFiles/export/view/export_view_progress.cpp create mode 100644 Telegram/SourceFiles/export/view/export_view_progress.h diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 4177a4af58837..b4d0dd86f5e5f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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 diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp index 4b8f504454ead..32f21622b3f81 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.cpp +++ b/Telegram/SourceFiles/export/data/export_data_types.cpp @@ -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; diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h index 93d85cf713eab..88b90baed8f90 100644 --- a/Telegram/SourceFiles/export/data/export_data_types.h +++ b/Telegram/SourceFiles/export/data/export_data_types.h @@ -427,6 +427,9 @@ struct DialogInfo { bool onlyMyMessages = false; QString relativePath; + // Filled when requesting dialog messages. + int messagesCount = 0; + }; struct DialogsInfo { diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index 73234fe6b6596..aa6d060b1d28a 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -9,6 +9,7 @@ For license and copyright information please follow this link: #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 start; - Fn handleSlice; + FnMut start; + Fn fileProgress; + Fn handleSlice; FnMut finish; base::optional slice; + uint64 maxId = 0; bool lastSlice = false; int fileIndex = -1; @@ -130,6 +133,7 @@ struct ApiWrap::FileProcess { Output::File file; QString relativePath; + Fn progress; FnMut done; Data::FileLocation location; @@ -144,18 +148,25 @@ struct ApiWrap::FileProcess { }; +struct ApiWrap::FileProgress { + int ready = 0; + int total = 0; +}; + struct ApiWrap::LeftChannelsProcess { + Fn progress; FnMut done; Data::DialogsInfo info; - rpl::variable count; int fullCount = 0; + int offset = 0; bool finished = false; }; struct ApiWrap::DialogsProcess { + Fn progress; FnMut done; Data::DialogsInfo info; @@ -164,14 +175,14 @@ struct ApiWrap::DialogsProcess { int32 offsetId = 0; MTPInputPeer offsetPeer = MTP_inputPeerEmpty(); - rpl::variable count; - }; struct ApiWrap::ChatProcess { Data::DialogInfo info; - Fn handleSlice; + FnMut start; + Fn fileProgress; + Fn handleSlice; FnMut done; int32 offsetId = 1; @@ -247,6 +258,10 @@ rpl::producer ApiWrap::errors() const { return _errors.events(); } +rpl::producer ApiWrap::ioErrors() const { + return _ioErrors.events(); +} + void ApiWrap::startExport( const Settings &settings, FnMut 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 progress, FnMut done) { Expects(_leftChannelsProcess != nullptr); + _leftChannelsProcess->progress = std::move(progress); _leftChannelsProcess->done = std::move(done); requestLeftChannelsSlice(); } @@ -382,27 +401,18 @@ void ApiWrap::requestLeftChannelsSlice() { }); } -rpl::producer ApiWrap::leftChannelsLoadedCount() const { - Expects(_leftChannelsProcess != nullptr); - - return _leftChannelsProcess->count.value(); -} - -void ApiWrap::requestDialogsList(FnMut done) { +void ApiWrap::requestDialogsList( + Fn progress, + FnMut done) { Expects(_dialogsProcess == nullptr); _dialogsProcess = std::make_unique(); + _dialogsProcess->progress = std::move(progress); _dialogsProcess->done = std::move(done); requestDialogsSlice(); } -rpl::producer ApiWrap::dialogsLoadedCount() const { - Expects(_dialogsProcess != nullptr); - - return _dialogsProcess->count.value(); -} - void ApiWrap::startMainSession(FnMut done) { const auto sizeLimit = _settings->media.sizeLimit; const auto hasFiles = (_settings->media.types != 0) && (sizeLimit > 0); @@ -456,33 +466,35 @@ void ApiWrap::requestPersonalInfo(FnMut done) { } void ApiWrap::requestUserpics( - FnMut start, - Fn slice, + FnMut start, + Fn progress, + Fn slice, FnMut finish) { Expects(_userpicsProcess == nullptr); _userpicsProcess = std::make_unique(); _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 done) { void ApiWrap::requestMessages( const Data::DialogInfo &info, - Fn slice, + FnMut start, + Fn progress, + Fn slice, FnMut done) { Expects(_chatProcess == nullptr); _chatProcess = std::make_unique(); _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 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 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 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 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()) { _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 progress, FnMut 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 progress, FnMut 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 diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index e101e420b898c..0475507175fc9 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -25,6 +25,10 @@ struct MessagesSlice; struct Message; } // namespace Data +namespace Output { +struct Result; +} // namespace Output + struct Settings; class ApiWrap { @@ -32,6 +36,7 @@ class ApiWrap { ApiWrap(Fn)> runner); rpl::producer errors() const; + rpl::producer ioErrors() const; struct StartInfo { int userpicsCount = 0; @@ -42,17 +47,24 @@ class ApiWrap { const Settings &settings, FnMut done); - void requestLeftChannelsList(FnMut done); - rpl::producer leftChannelsLoadedCount() const; - - void requestDialogsList(FnMut done); - rpl::producer dialogsLoadedCount() const; + void requestLeftChannelsList( + Fn progress, + FnMut done); + void requestDialogsList( + Fn progress, + FnMut done); void requestPersonalInfo(FnMut done); + struct DownloadProgress { + int itemIndex = 0; + int ready = 0; + int total = 0; + }; void requestUserpics( - FnMut start, - Fn slice, + FnMut start, + Fn progress, + Fn slice, FnMut finish); void requestContacts(FnMut done); @@ -61,7 +73,9 @@ class ApiWrap { void requestMessages( const Data::DialogInfo &info, - Fn slice, + FnMut start, + Fn progress, + Fn slice, FnMut done); ~ApiWrap(); @@ -71,6 +85,7 @@ class ApiWrap { struct StartProcess; struct UserpicsProcess; struct FileProcess; + struct FileProgress; struct LeftChannelsProcess; struct DialogsProcess; struct ChatProcess; @@ -85,7 +100,9 @@ class ApiWrap { 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 @@ class ApiWrap { void appendChatsSlice(Data::DialogsInfo &to, Data::DialogsInfo &&info); - void requestMessagesSlice(); + void requestMessagesSlice(FnMut 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 progress, FnMut done, Data::Message *message = nullptr); std::unique_ptr prepareFileProcess( @@ -114,6 +133,7 @@ class ApiWrap { bool writePreloadedFile(Data::File &file); void loadFile( const Data::File &file, + Fn progress, FnMut done); void loadFilePart(); void filePartDone(int offset, const MTPupload_File &result); @@ -127,6 +147,7 @@ class ApiWrap { void error(RPCError &&error); void error(const QString &text); + void ioError(const Output::Result &result); MTP::ConcurrentSender _mtp; base::optional _takeoutId; @@ -143,6 +164,7 @@ class ApiWrap { std::unique_ptr _chatProcess; rpl::event_stream _errors; + rpl::event_stream _ioErrors; }; diff --git a/Telegram/SourceFiles/export/export_controller.cpp b/Telegram/SourceFiles/export/export_controller.cpp index c590b1375be2e..ae37dbff948b5 100644 --- a/Telegram/SourceFiles/export/export_controller.cpp +++ b/Telegram/SourceFiles/export/export_controller.cpp @@ -11,6 +11,7 @@ For license and copyright information please follow this link: #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 @@ class Controller { 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 @@ class Controller { 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 @@ class Controller { 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 fails to compile in MSVC :( State _state; rpl::event_stream _stateChanges; - std::vector _substepsInStep; + std::shared_ptr> _substepsInStep; std::unique_ptr _writer; std::vector _steps; @@ -100,7 +111,12 @@ Controller::Controller(crl::weak_on_queue 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(); const auto push = [&](Step step, int count) { const auto index = static_cast(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>( + 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(step)); + Expects(_substepsInStep != 0); + Expects(_substepsInStep->size() > static_cast(step)); - return _substepsInStep[static_cast(step)]; + return (*_substepsInStep)[static_cast(step)]; } void Controller::setFinishedState() { diff --git a/Telegram/SourceFiles/export/export_controller.h b/Telegram/SourceFiles/export/export_controller.h index 22b89295833df..debde2181cbcc 100644 --- a/Telegram/SourceFiles/export/export_controller.h +++ b/Telegram/SourceFiles/export/export_controller.h @@ -50,7 +50,7 @@ struct ProcessingState { Step step = Step::Initializing; - std::vector substepsInStep; + std::shared_ptr> 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 apiError; - base::optional 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 { diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.h b/Telegram/SourceFiles/export/output/export_output_abstract.h index fbd2ba696b36d..c1f20b8ab660a 100644 --- a/Telegram/SourceFiles/export/output/export_output_abstract.h +++ b/Telegram/SourceFiles/export/output/export_output_abstract.h @@ -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; - - virtual bool 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; - - virtual bool writeContactsList(const Data::ContactsList &data) = 0; - - virtual bool 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; - - 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; - - virtual bool finish() = 0; - - virtual QString mainFilePath() = 0; + [[nodiscard]] virtual Result start(const Settings &settings) = 0; + + [[nodiscard]] virtual Result writePersonal( + const Data::PersonalInfo &data) = 0; + + [[nodiscard]] virtual Result writeUserpicsStart( + const Data::UserpicsInfo &data) = 0; + [[nodiscard]] virtual Result writeUserpicsSlice( + const Data::UserpicsSlice &data) = 0; + [[nodiscard]] virtual Result writeUserpicsEnd() = 0; + + [[nodiscard]] virtual Result writeContactsList( + const Data::ContactsList &data) = 0; + + [[nodiscard]] virtual Result writeSessionsList( + const Data::SessionsList &data) = 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; + + [[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; + + [[nodiscard]] virtual Result finish() = 0; + + [[nodiscard]] virtual QString mainFilePath() = 0; virtual ~AbstractWriter() = default; diff --git a/Telegram/SourceFiles/export/output/export_output_file.cpp b/Telegram/SourceFiles/export/output/export_output_file.cpp index e415b76524542..d16a92b5881f2 100644 --- a/Telegram/SourceFiles/export/output/export_output_file.cpp +++ b/Telegram/SourceFiles/export/output/export_output_file.cpp @@ -7,6 +7,8 @@ For license and copyright information please follow this link: */ #include "export/output/export_output_file.h" +#include "export/output/export_output_result.h" + #include #include @@ -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( diff --git a/Telegram/SourceFiles/export/output/export_output_file.h b/Telegram/SourceFiles/export/output/export_output_file.h index 32907682ee97e..b22b948590fb8 100644 --- a/Telegram/SourceFiles/export/output/export_output_file.h +++ b/Telegram/SourceFiles/export/output/export_output_file.h @@ -16,27 +16,27 @@ For license and copyright information please follow this link: 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; diff --git a/Telegram/SourceFiles/export/output/export_output_result.h b/Telegram/SourceFiles/export/output/export_output_result.h new file mode 100644 index 0000000000000..a715e6481d646 --- /dev/null +++ b/Telegram/SourceFiles/export/output/export_output_result.h @@ -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 + +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 diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp index 03ccec333bcc1..769026efccc18 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.cpp +++ b/Telegram/SourceFiles/export/output/export_output_text.cpp @@ -7,6 +7,7 @@ For license and copyright information please follow this link: */ #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() { diff --git a/Telegram/SourceFiles/export/output/export_output_text.h b/Telegram/SourceFiles/export/output/export_output_text.h index cdb994eaf3ae5..7ca08073681b5 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.h +++ b/Telegram/SourceFiles/export/output/export_output_text.h @@ -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 @@ class TextWriter : public AbstractWriter { QString pathWithRelativePath(const QString &path) const; std::unique_ptr 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 _result; + std::unique_ptr _summary; int _userpicsCount = 0; int _dialogsCount = 0; @@ -67,10 +67,6 @@ class TextWriter : public AbstractWriter { bool _dialogOnlyMy = false; bool _dialogEmpty = true; - int _leftChannelsCount = 0; - int _leftChannelIndex = 0; - bool _leftChannelEmpty = true; - std::unique_ptr _chat; }; diff --git a/Telegram/SourceFiles/export/view/export.style b/Telegram/SourceFiles/export/view/export.style index 96ba03d9abf35..08f71ff64cc42 100644 --- a/Telegram/SourceFiles/export/view/export.style +++ b/Telegram/SourceFiles/export/view/export.style @@ -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; +} diff --git a/Telegram/SourceFiles/export/view/export_view_content.cpp b/Telegram/SourceFiles/export/view/export_view_content.cpp new file mode 100644 index 0000000000000..8122e9ca4ea8c --- /dev/null +++ b/Telegram/SourceFiles/export/view/export_view_content.cpp @@ -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 diff --git a/Telegram/SourceFiles/export/view/export_view_content.h b/Telegram/SourceFiles/export/view/export_view_content.h new file mode 100644 index 0000000000000..66aa7007c2bd2 --- /dev/null +++ b/Telegram/SourceFiles/export/view/export_view_content.h @@ -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 rows; + +}; + +Content ContentFromState(const ProcessingState &state); + +inline auto ContentFromState(rpl::producer state) { + return std::move( + state + ) | rpl::filter([](const State &state) { + return state.template is(); + }) | rpl::map([](const State &state) { + return ContentFromState( + state.template get_unchecked()); + }); +} + +} // namespace View +} // namespace Export diff --git a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp index bcdcd4a4abfd1..e5d499a9d0f0a 100644 --- a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp +++ b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp @@ -8,8 +8,11 @@ For license and copyright information please follow this link: #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( + _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>( + _panel.get(), + object_ptr( + _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(&_state)) { + if (const auto apiError = base::get_if(&_state)) { + showError(*apiError); + } else if (const auto error = base::get_if(&_state)) { + showError(*error); + } else if (const auto finished = base::get_if(&_state)) { const auto path = finished->path; auto done = base::make_unique_q(_panel.get()); diff --git a/Telegram/SourceFiles/export/view/export_view_panel_controller.h b/Telegram/SourceFiles/export/view/export_view_panel_controller.h index 9048a9004bd3b..0a9149c33b7b6 100644 --- a/Telegram/SourceFiles/export/view/export_view_panel_controller.h +++ b/Telegram/SourceFiles/export/view/export_view_panel_controller.h @@ -31,6 +31,9 @@ class PanelController { 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 _process; diff --git a/Telegram/SourceFiles/export/view/export_view_progress.cpp b/Telegram/SourceFiles/export/view/export_view_progress.cpp new file mode 100644 index 0000000000000..71d2034b41152 --- /dev/null +++ b/Telegram/SourceFiles/export/view/export_view_progress.cpp @@ -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) +: RpWidget(parent) { + const auto label = Ui::CreateChild(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 diff --git a/Telegram/SourceFiles/export/view/export_view_progress.h b/Telegram/SourceFiles/export/view/export_view_progress.h new file mode 100644 index 0000000000000..c78692bdd9206 --- /dev/null +++ b/Telegram/SourceFiles/export/view/export_view_progress.h @@ -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); + + ~ProgressWidget(); + +private: + void updateState(Content &&content); + + Content _content; + + class Row; + std::vector> _rows; + +}; + +} // namespace View +} // namespace Export diff --git a/Telegram/SourceFiles/mtproto/concurrent_sender.cpp b/Telegram/SourceFiles/mtproto/concurrent_sender.cpp index 93b473d59b44b..94652d716d1fd 100644 --- a/Telegram/SourceFiles/mtproto/concurrent_sender.cpp +++ b/Telegram/SourceFiles/mtproto/concurrent_sender.cpp @@ -9,6 +9,7 @@ For license and copyright information please follow this link: #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())); + } } } diff --git a/Telegram/SourceFiles/mtproto/concurrent_sender.h b/Telegram/SourceFiles/mtproto/concurrent_sender.h index 7bb9d2f789557..883076cb64c75 100644 --- a/Telegram/SourceFiles/mtproto/concurrent_sender.h +++ b/Telegram/SourceFiles/mtproto/concurrent_sender.h @@ -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(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(result.data()); + const auto end = from + result.size() / sizeof(mtpPrime); + Response data; + data.read(from, end); + std::move(handler)(requestId, std::move(data)); }; } diff --git a/Telegram/gyp/telegram_sources.txt b/Telegram/gyp/telegram_sources.txt index beeca2b68799b..f286f15aa3dd7 100644 --- a/Telegram/gyp/telegram_sources.txt +++ b/Telegram/gyp/telegram_sources.txt @@ -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