From 54cab2c5a502c9ea3171cfe029c2bcba5628e995 Mon Sep 17 00:00:00 2001 From: John Preston Date: Sun, 24 Jun 2018 01:33:47 +0100 Subject: [PATCH] Add other additional data export. --- Telegram/Resources/langs/lang.strings | 10 ++- Telegram/Resources/scheme.tl | 1 + Telegram/SourceFiles/data/data_session.cpp | 4 + .../SourceFiles/export/export_api_wrap.cpp | 54 ++++++++++++- Telegram/SourceFiles/export/export_api_wrap.h | 8 ++ .../SourceFiles/export/export_controller.cpp | 30 +++++++- .../SourceFiles/export/export_controller.h | 1 + .../SourceFiles/export/export_settings.cpp | 2 +- Telegram/SourceFiles/export/export_settings.h | 15 ++-- .../export/output/export_output_abstract.h | 4 + .../export/output/export_output_html.cpp | 21 +++++- .../export/output/export_output_html.h | 2 + .../export/output/export_output_json.cpp | 75 +++++++++++++++++++ .../export/output/export_output_json.h | 2 + .../export/output/export_output_text.cpp | 17 ++++- .../export/output/export_output_text.h | 2 + .../export/view/export_view_content.cpp | 3 + .../view/export_view_panel_controller.cpp | 22 +++++- .../export/view/export_view_settings.cpp | 12 ++- .../export/view/export_view_settings.h | 4 +- 20 files changed, 260 insertions(+), 29 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b46a9e68f..11c8b4e0a 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1660,8 +1660,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_export_option_info_about" = "Your chosen screen name, username, phone number and profile pictures."; "lng_export_option_contacts" = "Contacts list"; "lng_export_option_contacts_about" = "If you allow access, contacts are continuously synced with Telegram. You can adjust this in Settings > Privacy & Security on mobile devices."; -"lng_export_option_sessions" = "Sessions list"; +"lng_export_option_sessions" = "Active sessions"; "lng_export_option_sessions_about" = "We store this to display your connected devices in Settings > Privacy & Security > Active Sessions. Terminating a session removes this data from Telegram servers."; +"lng_export_header_other" = "Other"; +"lng_export_option_other" = "Miscellaneous data"; +"lng_export_option_other_about" = "Other types of data not mentioned above. (beta)"; "lng_export_header_chats" = "Chat export settings"; "lng_export_option_personal_chats" = "Personal chats"; "lng_export_option_bot_chats" = "Bot chats"; @@ -1705,7 +1708,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_export_total_size" = "Total size: {size}."; "lng_export_folder" = "Choose export folder"; "lng_export_invalid" = "Sorry, you have started a new data export, so this data export is now cancelled."; -"lng_export_delay" = "Sorry, for security reasons, you will be able to begin downloading your data in 24 hours. We have notified all your devices about the export request to make sure it's authorized and give you time to react if it's not.\n\nPlease come back on {date} and repeat the request using the same device."; +"lng_export_delay" = "Sorry, for security reasons, you will be able to begin downloading your data in {hours}. We have notified all your devices about the export request to make sure it's authorized and give you time to react if it's not.\n\nPlease come back on {date} and repeat the request using the same device."; +"lng_export_delay_less_than_hour" = "less than an hour"; +"lng_export_delay_hours#one" = "{count} hour"; +"lng_export_delay_hours#other" = "{count} hours"; "lng_export_suggest_title" = "Data export ready"; "lng_export_suggest_text" = "You can now download the data you requested. Start exporting data?"; "lng_export_suggest_cancel" = "Not now"; diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl index 48e6a2e77..7b022aaa4 100644 --- a/Telegram/Resources/scheme.tl +++ b/Telegram/Resources/scheme.tl @@ -192,6 +192,7 @@ inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLo inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation; inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation; inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation; +inputTakeoutFileLocation#29be5899 = InputFileLocation; inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 0f96a9643..ad57e3962 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -71,6 +71,10 @@ Session::Session(not_null session) } void Session::startExport() { + if (_exportPanel) { + _exportPanel->activatePanel(); + return; + } _export = std::make_unique(); _exportPanel = std::make_unique( _export.get()); diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp index c7e5c404a..7f5316b54 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.cpp +++ b/Telegram/SourceFiles/export/export_api_wrap.cpp @@ -62,6 +62,8 @@ LocationKey ComputeLocationKey(const Data::FileLocation &value) { }, [&](const MTPDinputEncryptedFileLocation &data) { result.type |= (4ULL << 24); result.id = data.vid.v; + }, [&](const MTPDinputTakeoutFileLocation &data) { + result.type |= (5ULL << 24); }); return result; } @@ -140,6 +142,11 @@ struct ApiWrap::UserpicsProcess { int fileIndex = -1; }; +struct ApiWrap::OtherDataProcess { + Data::File file; + FnMut done; +}; + struct ApiWrap::FileProcess { FileProcess(const QString &path, Output::Stats *stats); @@ -259,7 +266,8 @@ auto ApiWrap::splitRequest(int index, Request &&request) { } auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) { - Expects(location.dcId != 0); + Expects(location.dcId != 0 + || location.data.type() == mtpc_inputTakeoutFileLocation); Expects(_takeoutId.has_value()); return std::move(_mtp.request(MTPInvokeWithTakeout( @@ -269,7 +277,14 @@ auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) { MTP_int(offset), MTP_int(kFileChunkSize)) )).fail([=](RPCError &&result) { - error(std::move(result)); + if (result.type() == qstr("TAKEOUT_FILE_EMPTY") + && _otherDataProcess != nullptr) { + filePartDone(0, MTP_upload_file(MTP_storage_filePartial(), + MTP_int(0), + MTP_bytes(QByteArray()))); + } else { + error(std::move(result)); + } }).toDC(MTP::ShiftDcId(location.dcId, MTP::kExportMediaDcShift))); } @@ -376,6 +391,8 @@ void ApiWrap::requestSplitRanges() { void ApiWrap::requestDialogsCount() { Expects(_startProcess != nullptr); + validateSplits(); + splitRequest(_startProcess->splitIndex, MTPmessages_GetDialogs( MTP_flags(0), MTP_int(0), // offset_date @@ -467,7 +484,7 @@ void ApiWrap::requestDialogsList( void ApiWrap::validateSplits() { if (_splits.empty()) { _splits.push_back(MTP_messageRange( - MTP_int(0), + MTP_int(1), MTP_int(std::numeric_limits::max()))); } } @@ -524,6 +541,29 @@ void ApiWrap::requestPersonalInfo(FnMut done) { }).send(); } +void ApiWrap::requestOtherData( + const QString &suggestedPath, + FnMut done) { + Expects(_otherDataProcess == nullptr); + + _otherDataProcess = std::make_unique(); + _otherDataProcess->done = std::move(done); + _otherDataProcess->file.location.data = MTP_inputTakeoutFileLocation(); + _otherDataProcess->file.suggestedPath = suggestedPath; + loadFile( + _otherDataProcess->file, + [](FileProgress progress) { return true; }, + [=](const QString &result) { otherDataDone(result); }); +} + +void ApiWrap::otherDataDone(const QString &relativePath) { + Expects(_otherDataProcess != nullptr); + + _otherDataProcess->file.relativePath = relativePath; + const auto process = base::take(_otherDataProcess); + process->done(std::move(process->file)); +} + void ApiWrap::requestUserpics( FnMut start, Fn progress, @@ -1185,7 +1225,8 @@ void ApiWrap::loadFile( Fn progress, FnMut done) { Expects(_fileProcess == nullptr); - Expects(file.location.dcId != 0); + Expects(file.location.dcId != 0 + || file.location.data.type() == mtpc_inputTakeoutFileLocation); _fileProcess = prepareFileProcess(file); _fileProcess->progress = std::move(progress); @@ -1265,6 +1306,11 @@ void ApiWrap::filePartDone(int offset, const MTPupload_File &result) { error("Empty bytes received in file part."); return; } + const auto result = _fileProcess->file.writeBlock({}); + if (!result) { + ioError(result); + return; + } } else { using Request = FileProcess::Request; auto &requests = _fileProcess->requests; diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h index 32f5c0833..7e5a59c6e 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -58,6 +58,10 @@ public: void requestPersonalInfo(FnMut done); + void requestOtherData( + const QString &suggestedPath, + FnMut done); + struct DownloadProgress { QString path; int itemIndex = 0; @@ -91,6 +95,7 @@ private: struct StartProcess; struct ContactsProcess; struct UserpicsProcess; + struct OtherDataProcess; struct FileProcess; struct FileProgress; struct ChatsProcess; @@ -116,6 +121,8 @@ private: void finishUserpicsSlice(); void finishUserpics(); + void otherDataDone(const QString &relativePath); + void validateSplits(); void requestDialogsSlice(); @@ -186,6 +193,7 @@ private: std::unique_ptr _fileCache; std::unique_ptr _contactsProcess; std::unique_ptr _userpicsProcess; + std::unique_ptr _otherDataProcess; std::unique_ptr _fileProcess; std::unique_ptr _leftChannelsProcess; std::unique_ptr _dialogsProcess; diff --git a/Telegram/SourceFiles/export/export_controller.cpp b/Telegram/SourceFiles/export/export_controller.cpp index 7cb980877..985ed971b 100644 --- a/Telegram/SourceFiles/export/export_controller.cpp +++ b/Telegram/SourceFiles/export/export_controller.cpp @@ -57,6 +57,7 @@ private: void exportUserpics(); void exportContacts(); void exportSessions(); + void exportOtherData(); void exportDialogs(); void exportNextDialog(); void exportLeftChannels(); @@ -73,6 +74,7 @@ private: ProcessingState stateUserpics(const DownloadProgress &progress) const; ProcessingState stateContacts() const; ProcessingState stateSessions() const; + ProcessingState stateOtherData() const; ProcessingState stateLeftChannels( const DownloadProgress &progress) const; ProcessingState stateDialogs(const DownloadProgress &progress) const; @@ -250,6 +252,9 @@ void Controller::fillExportSteps() { if (_settings.types & Type::Sessions) { _steps.push_back(Step::Sessions); } + if (_settings.types & Type::OtherData) { + _steps.push_back(Step::OtherData); + } if (_settings.types & Type::AnyChatsMask) { _steps.push_back(Step::Dialogs); } @@ -286,6 +291,9 @@ void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) { if (_settings.types & Settings::Type::Sessions) { push(Step::Sessions, 1); } + if (_settings.types & Settings::Type::OtherData) { + push(Step::OtherData, 1); + } if (_settings.types & Settings::Type::GroupsChannelsMask) { push(Step::LeftChannels, info.leftChannelsCount); } @@ -321,6 +329,7 @@ void Controller::exportNext() { case Step::Userpics: return exportUserpics(); case Step::Contacts: return exportContacts(); case Step::Sessions: return exportSessions(); + case Step::OtherData: return exportOtherData(); case Step::LeftChannels: return exportLeftChannels(); case Step::Dialogs: return exportDialogs(); } @@ -329,7 +338,6 @@ void Controller::exportNext() { void Controller::initialize() { setState(stateInitializing()); - _api.startExport(_settings, &_stats, [=](ApiWrap::StartInfo info) { if (ioCatchError(_writer->start(_settings, &_stats))) { return; @@ -340,6 +348,7 @@ void Controller::initialize() { } void Controller::collectLeftChannels() { + setState(stateLeftChannelsList(0)); _api.requestLeftChannelsList([=](int count) { setState(stateLeftChannelsList(count)); return true; @@ -350,6 +359,7 @@ void Controller::collectLeftChannels() { } void Controller::collectDialogsList() { + setState(stateDialogsList(0)); _api.requestDialogsList([=](int count) { setState(stateDialogsList(count)); return true; @@ -360,6 +370,7 @@ void Controller::collectDialogsList() { } void Controller::exportPersonalInfo() { + setState(statePersonalInfo()); _api.requestPersonalInfo([=](Data::PersonalInfo &&result) { if (ioCatchError(_writer->writePersonal(result))) { return; @@ -395,6 +406,7 @@ void Controller::exportUserpics() { } void Controller::exportContacts() { + setState(stateContacts()); _api.requestContacts([=](Data::ContactsList &&result) { if (ioCatchError(_writer->writeContactsList(result))) { return; @@ -404,6 +416,7 @@ void Controller::exportContacts() { } void Controller::exportSessions() { + setState(stateSessions()); _api.requestSessions([=](Data::SessionsList &&result) { if (ioCatchError(_writer->writeSessionsList(result))) { return; @@ -412,6 +425,17 @@ void Controller::exportSessions() { }); } +void Controller::exportOtherData() { + setState(stateOtherData()); + const auto relativePath = "lists/other_data.json"; + _api.requestOtherData(relativePath, [=](Data::File &&result) { + if (ioCatchError(_writer->writeOtherData(result))) { + return; + } + exportNext(); + }); +} + void Controller::exportDialogs() { if (ioCatchError(_writer->writeDialogsStart(_dialogsInfo))) { return; @@ -573,6 +597,10 @@ ProcessingState Controller::stateSessions() const { return prepareState(Step::Sessions); } +ProcessingState Controller::stateOtherData() const { + return prepareState(Step::OtherData); +} + ProcessingState Controller::stateLeftChannels( const DownloadProgress & progress) const { const auto step = Step::LeftChannels; diff --git a/Telegram/SourceFiles/export/export_controller.h b/Telegram/SourceFiles/export/export_controller.h index 9d38f1882..ac1928438 100644 --- a/Telegram/SourceFiles/export/export_controller.h +++ b/Telegram/SourceFiles/export/export_controller.h @@ -33,6 +33,7 @@ struct ProcessingState { Userpics, Contacts, Sessions, + OtherData, LeftChannels, Dialogs, }; diff --git a/Telegram/SourceFiles/export/export_settings.cpp b/Telegram/SourceFiles/export/export_settings.cpp index 082f90bcd..d44e17fcb 100644 --- a/Telegram/SourceFiles/export/export_settings.cpp +++ b/Telegram/SourceFiles/export/export_settings.cpp @@ -37,7 +37,7 @@ bool Settings::validate() const { return false; } else if ((fullChats & MustNotBeFull) != 0) { return false; - } else if (format != Format::Text && format != Format::Json) { + } else if (format != Format::Html && format != Format::Json) { return false; } else if (!media.validate()) { return false; diff --git a/Telegram/SourceFiles/export/export_settings.h b/Telegram/SourceFiles/export/export_settings.h index 129d43f73..123c2c624 100644 --- a/Telegram/SourceFiles/export/export_settings.h +++ b/Telegram/SourceFiles/export/export_settings.h @@ -50,12 +50,13 @@ struct Settings { Userpics = 0x002, Contacts = 0x004, Sessions = 0x008, - PersonalChats = 0x010, - BotChats = 0x020, - PrivateGroups = 0x040, - PublicGroups = 0x080, - PrivateChannels = 0x100, - PublicChannels = 0x200, + OtherData = 0x010, + PersonalChats = 0x020, + BotChats = 0x040, + PrivateGroups = 0x080, + PublicGroups = 0x100, + PrivateChannels = 0x200, + PublicChannels = 0x400, GroupsMask = PrivateGroups | PublicGroups, ChannelsMask = PrivateChannels | PublicChannels, @@ -63,7 +64,7 @@ struct Settings { NonChannelChatsMask = PersonalChats | BotChats | PrivateGroups, AnyChatsMask = PersonalChats | BotChats | GroupsChannelsMask, NonChatsMask = PersonalInfo | Userpics | Contacts | Sessions, - AllMask = NonChatsMask | AnyChatsMask, + AllMask = NonChatsMask | OtherData | AnyChatsMask, }; using Types = base::flags; friend inline constexpr auto is_flag_type(Type) { return true; }; diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.h b/Telegram/SourceFiles/export/output/export_output_abstract.h index b0a485ca9..e28ae4e7a 100644 --- a/Telegram/SourceFiles/export/output/export_output_abstract.h +++ b/Telegram/SourceFiles/export/output/export_output_abstract.h @@ -19,6 +19,7 @@ struct SessionsList; struct DialogsInfo; struct DialogInfo; struct MessagesSlice; +struct File; } // namespace Data struct Settings; @@ -60,6 +61,9 @@ public: [[nodiscard]] virtual Result writeSessionsList( const Data::SessionsList &data) = 0; + [[nodiscard]] virtual Result writeOtherData( + const Data::File &data) = 0; + [[nodiscard]] virtual Result writeDialogsStart( const Data::DialogsInfo &data) = 0; [[nodiscard]] virtual Result writeDialogStart( diff --git a/Telegram/SourceFiles/export/output/export_output_html.cpp b/Telegram/SourceFiles/export/output/export_output_html.cpp index da1c404a6..9bfda6cd3 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.cpp +++ b/Telegram/SourceFiles/export/output/export_output_html.cpp @@ -637,7 +637,7 @@ QByteArray HtmlWriter::Wrap::end() const { } HtmlWriter::Wrap::~Wrap() { - Expects(_file.empty() || _closed); + (void)close(); } HtmlWriter::HtmlWriter() = default; @@ -1019,6 +1019,17 @@ Result HtmlWriter::writeWebSessions(const Data::SessionsList &data) { return _summary->writeBlock(header); } +Result HtmlWriter::writeOtherData(const Data::File &data) { + Expects(_summary != nullptr); + + const auto header = SerializeLink( + "Other data", + _summary->relativePath(data)) + + kLineBreak + + kLineBreak; + return _summary->writeBlock(header); +} + Result HtmlWriter::writeDialogsStart(const Data::DialogsInfo &data) { return writeChatsStart( data, @@ -1123,7 +1134,9 @@ Result HtmlWriter::writeChatSlice(const Data::MessagesSlice &data) { data.peers, _settings.internalLinksDomain)); } - const auto full = kLineBreak + JoinList(kLineBreak, list); + const auto full = _chat->empty() + ? JoinList(kLineBreak, list) + : kLineBreak + JoinList(kLineBreak, list); return _chat->writeBlock(full); } @@ -1172,7 +1185,7 @@ Result HtmlWriter::writeChatEnd() { } Unexpected("Dialog type in TypeString."); }; - return _chats->writeBlock(SerializeKeyValue({ + return _chats->writeBlock(kLineBreak + SerializeKeyValue({ { "Name", SerializeString(NameString(_dialog, _dialog.type)) }, { "Type", SerializeString(TypeString(_dialog.type)) }, { @@ -1190,7 +1203,7 @@ Result HtmlWriter::writeChatEnd() { (_dialog.relativePath + "messages.html"))) : QByteArray()) } - }) + kLineBreak); + })); } Result HtmlWriter::writeChatsEnd() { diff --git a/Telegram/SourceFiles/export/output/export_output_html.h b/Telegram/SourceFiles/export/output/export_output_html.h index fad9725b2..6a1fd8e2f 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.h +++ b/Telegram/SourceFiles/export/output/export_output_html.h @@ -35,6 +35,8 @@ public: Result writeSessionsList(const Data::SessionsList &data) override; + Result writeOtherData(const Data::File &data) override; + Result writeDialogsStart(const Data::DialogsInfo &data) override; Result writeDialogStart(const Data::DialogInfo &data) override; Result writeDialogSlice(const Data::MessagesSlice &data) override; diff --git a/Telegram/SourceFiles/export/output/export_output_json.cpp b/Telegram/SourceFiles/export/output/export_output_json.cpp index 5627b9083..9300b3009 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -13,6 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/utils.h" #include +#include +#include +#include +#include namespace Export { namespace Output { @@ -793,6 +797,77 @@ Result JsonWriter::writeSessionsList(const Data::SessionsList &data) { return Result::Success(); } +Result JsonWriter::writeOtherData(const Data::File &data) { + Expects(_output != nullptr); + Expects(data.skipReason == Data::File::SkipReason::None); + Expects(!data.relativePath.isEmpty()); + + QFile f(pathWithRelativePath(data.relativePath)); + if (!f.open(QIODevice::ReadOnly)) { + return Result(Result::Type::FatalError, f.fileName()); + } + const auto content = f.readAll(); + if (content.isEmpty()) { + return Result::Success(); + } + auto error = QJsonParseError{ 0, QJsonParseError::NoError }; + const auto document = QJsonDocument::fromJson(content, &error); + if (error.error != QJsonParseError::NoError) { + return Result(Result::Type::FatalError, f.fileName()); + } + auto block = prepareObjectItemStart("other_data"); + Fn pushObject; + Fn pushArray; + Fn pushValue; + pushObject = [&](const QJsonObject &data) { + block.append(pushNesting(Context::kObject)); + for (auto i = data.begin(); i != data.end(); ++i) { + if ((*i).type() != QJsonValue::Undefined) { + block.append(prepareObjectItemStart(i.key().toUtf8())); + pushValue(*i); + } + } + block.append(popNesting()); + }; + pushArray = [&](const QJsonArray &data) { + block.append(pushNesting(Context::kArray)); + for (auto i = data.begin(); i != data.end(); ++i) { + if ((*i).type() != QJsonValue::Undefined) { + block.append(prepareArrayItemStart()); + pushValue(*i); + } + } + block.append(popNesting()); + }; + pushValue = [&](const QJsonValue &data) { + switch (data.type()) { + case QJsonValue::Null: + block.append("null"); + return; + case QJsonValue::Bool: + block.append(data.toBool() ? "true" : "false"); + return; + case QJsonValue::Double: + block.append(Data::NumberToString(data.toDouble())); + return; + case QJsonValue::String: + block.append(SerializeString(data.toString().toUtf8())); + return; + case QJsonValue::Array: + return pushArray(data.toArray()); + case QJsonValue::Object: + return pushObject(data.toObject()); + } + Unexpected("Type of json valuein JsonWriter::writeOtherData."); + }; + if (document.isObject()) { + pushObject(document.object()); + } else { + pushArray(document.array()); + } + return _output->writeBlock(block); +} + Result JsonWriter::writeSessions(const Data::SessionsList &data) { Expects(_output != nullptr); diff --git a/Telegram/SourceFiles/export/output/export_output_json.h b/Telegram/SourceFiles/export/output/export_output_json.h index 49d535510..fb59c50ea 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.h +++ b/Telegram/SourceFiles/export/output/export_output_json.h @@ -45,6 +45,8 @@ public: Result writeSessionsList(const Data::SessionsList &data) override; + Result writeOtherData(const Data::File &data) override; + Result writeDialogsStart(const Data::DialogsInfo &data) override; Result writeDialogStart(const Data::DialogInfo &data) override; Result writeDialogSlice(const Data::MessagesSlice &data) override; diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp index b3f32c926..4c578c4cf 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.cpp +++ b/Telegram/SourceFiles/export/output/export_output_text.cpp @@ -762,6 +762,15 @@ Result TextWriter::writeWebSessions(const Data::SessionsList &data) { return _summary->writeBlock(header); } +Result TextWriter::writeOtherData(const Data::File &data) { + Expects(_summary != nullptr); + + const auto header = "Other data - " + data.relativePath.toUtf8() + + kLineBreak + + kLineBreak; + return _summary->writeBlock(header); +} + Result TextWriter::writeDialogsStart(const Data::DialogsInfo &data) { return writeChatsStart( data, @@ -864,7 +873,9 @@ Result TextWriter::writeChatSlice(const Data::MessagesSlice &data) { data.peers, _settings.internalLinksDomain)); } - const auto full = kLineBreak + JoinList(kLineBreak, list); + const auto full = _chat->empty() + ? JoinList(kLineBreak, list) + : kLineBreak + JoinList(kLineBreak, list); return _chat->writeBlock(full); } @@ -911,7 +922,7 @@ Result TextWriter::writeChatEnd() { } Unexpected("Dialog type in TypeString."); }; - return _chats->writeBlock(SerializeKeyValue({ + return _chats->writeBlock(kLineBreak + SerializeKeyValue({ { "Name", NameString(_dialog, _dialog.type) }, { "Type", TypeString(_dialog.type) }, { @@ -926,7 +937,7 @@ Result TextWriter::writeChatEnd() { ? (_dialog.relativePath + "messages.txt").toUtf8() : QByteArray()) } - }) + kLineBreak); + })); } Result TextWriter::writeChatsEnd() { diff --git a/Telegram/SourceFiles/export/output/export_output_text.h b/Telegram/SourceFiles/export/output/export_output_text.h index 2c7265394..3d966236d 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.h +++ b/Telegram/SourceFiles/export/output/export_output_text.h @@ -33,6 +33,8 @@ public: Result writeSessionsList(const Data::SessionsList &data) override; + Result writeOtherData(const Data::File &data) override; + Result writeDialogsStart(const Data::DialogsInfo &data) override; Result writeDialogStart(const Data::DialogInfo &data) override; Result writeDialogSlice(const Data::MessagesSlice &data) override; diff --git a/Telegram/SourceFiles/export/view/export_view_content.cpp b/Telegram/SourceFiles/export/view/export_view_content.cpp index 60fcda275..f358a2d13 100644 --- a/Telegram/SourceFiles/export/view/export_view_content.cpp +++ b/Telegram/SourceFiles/export/view/export_view_content.cpp @@ -80,6 +80,9 @@ Content ContentFromState(const ProcessingState &state) { case Step::Sessions: pushMain(lang(lng_export_option_sessions)); break; + case Step::OtherData: + pushMain(lang(lng_export_option_other)); + break; case Step::LeftChannels: case Step::Dialogs: pushMain(lang(lng_export_state_chats)); diff --git a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp index f2f86deb2..ed5e9fb75 100644 --- a/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp +++ b/Telegram/SourceFiles/export/view/export_view_panel_controller.cpp @@ -91,8 +91,10 @@ void SuggestStart() { void ClearSuggestStart() { auto settings = Local::ReadExportSettings(); - settings.availableAt = 0; - Local::WriteExportSettings(settings); + if (settings.availableAt) { + settings.availableAt = 0; + Local::WriteExportSettings(settings); + } } PanelController::PanelController(not_null process) @@ -160,7 +162,18 @@ void PanelController::showError(const ApiErrorState &error) { qstr("TAKEOUT_INIT_DELAY_").size()).toInt(), 1); const auto now = QDateTime::currentDateTime(); const auto when = now.addSecs(seconds); - showError(lng_export_delay(lt_date, langDateTimeFull(when))); + const auto hours = seconds / 3600; + const auto hoursText = [&] { + if (hours <= 0) { + return lang(lng_export_delay_less_than_hour); + } + return lng_export_delay_hours(lt_count, hours); + }(); + showError(lng_export_delay( + lt_hours, + hoursText, + lt_date, + langDateTimeFull(when))); _settings->availableAt = unixtime() + seconds; _saveSettingsTimer.callOnce(kSaveSettingsTimeout); @@ -217,6 +230,9 @@ void PanelController::showError(const QString &text) { } void PanelController::showProgress() { + _settings->availableAt = 0; + ClearSuggestStart(); + _panel->setTitle(Lang::Viewer(lng_export_progress_title)); auto progress = base::make_unique_q( diff --git a/Telegram/SourceFiles/export/view/export_view_settings.cpp b/Telegram/SourceFiles/export/view/export_view_settings.cpp index b873f4c13..b9ee8f204 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.cpp +++ b/Telegram/SourceFiles/export/view/export_view_settings.cpp @@ -134,6 +134,13 @@ void SettingsWidget::setupOptions(not_null container) { Type::PublicChannels); setupMediaOptions(container); + + addHeader(container, lng_export_header_other); + addOptionWithAbout( + container, + lng_export_option_other, + Type::OtherData, + lng_export_option_other_about); } void SettingsWidget::setupMediaOptions( @@ -320,12 +327,12 @@ not_null SettingsWidget::addOption( return checkbox; } -void SettingsWidget::addOptionWithAbout( +not_null SettingsWidget::addOptionWithAbout( not_null container, LangKey key, Types types, LangKey about) { - addOption(container, key, types); + const auto result = addOption(container, key, types); const auto label = container->add( object_ptr( container, @@ -333,6 +340,7 @@ void SettingsWidget::addOptionWithAbout( Ui::FlatLabel::InitType::Simple, st::exportAboutOptionLabel), st::exportAboutOptionPadding); + return result; } void SettingsWidget::addChatOption( diff --git a/Telegram/SourceFiles/export/view/export_view_settings.h b/Telegram/SourceFiles/export/view/export_view_settings.h index 34c804afa..13cbc061c 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.h +++ b/Telegram/SourceFiles/export/view/export_view_settings.h @@ -51,12 +51,12 @@ private: not_null container, LangKey key, Types types); - void addOptionWithAbout( + not_null addOptionWithAbout( not_null container, LangKey key, Types types, LangKey about); - void addChatOption( + void addChatOption( not_null container, LangKey key, Types types);