diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b46a9e68f5704..11c8b4e0a366d 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 48e6a2e7778fe..7b022aaa4ea0d 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 0f96a96439554..ad57e3962707a 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 c7e5c404abe58..7f5316b54ecfd 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 32f5c08331bed..7e5a59c6e2764 100644 --- a/Telegram/SourceFiles/export/export_api_wrap.h +++ b/Telegram/SourceFiles/export/export_api_wrap.h @@ -58,6 +58,10 @@ class ApiWrap { void requestPersonalInfo(FnMut done); + void requestOtherData( + const QString &suggestedPath, + FnMut done); + struct DownloadProgress { QString path; int itemIndex = 0; @@ -91,6 +95,7 @@ class ApiWrap { struct StartProcess; struct ContactsProcess; struct UserpicsProcess; + struct OtherDataProcess; struct FileProcess; struct FileProgress; struct ChatsProcess; @@ -116,6 +121,8 @@ class ApiWrap { void finishUserpicsSlice(); void finishUserpics(); + void otherDataDone(const QString &relativePath); + void validateSplits(); void requestDialogsSlice(); @@ -186,6 +193,7 @@ class ApiWrap { 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 7cb980877cd88..985ed971b5c21 100644 --- a/Telegram/SourceFiles/export/export_controller.cpp +++ b/Telegram/SourceFiles/export/export_controller.cpp @@ -57,6 +57,7 @@ class Controller { void exportUserpics(); void exportContacts(); void exportSessions(); + void exportOtherData(); void exportDialogs(); void exportNextDialog(); void exportLeftChannels(); @@ -73,6 +74,7 @@ class Controller { 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 9d38f1882d347..ac1928438a28d 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 082f90bcdfb42..d44e17fcbc9ab 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 129d43f73b08f..123c2c624be69 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 b0a485ca9d986..e28ae4e7a2d6c 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 @@ class AbstractWriter { [[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 da1c404a6e2a0..9bfda6cd3cd49 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 fad9725b29093..6a1fd8e2fa222 100644 --- a/Telegram/SourceFiles/export/output/export_output_html.h +++ b/Telegram/SourceFiles/export/output/export_output_html.h @@ -35,6 +35,8 @@ class HtmlWriter : public AbstractWriter { 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 5627b9083ceca..9300b3009f5b8 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.cpp +++ b/Telegram/SourceFiles/export/output/export_output_json.cpp @@ -13,6 +13,10 @@ For license and copyright information please follow this link: #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 49d5355109b9d..fb59c50ea3a3e 100644 --- a/Telegram/SourceFiles/export/output/export_output_json.h +++ b/Telegram/SourceFiles/export/output/export_output_json.h @@ -45,6 +45,8 @@ class JsonWriter : public AbstractWriter { 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 b3f32c92661c4..4c578c4cf5b95 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 2c7265394b4d1..3d966236dc830 100644 --- a/Telegram/SourceFiles/export/output/export_output_text.h +++ b/Telegram/SourceFiles/export/output/export_output_text.h @@ -33,6 +33,8 @@ class TextWriter : public AbstractWriter { 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 60fcda275af82..f358a2d13d8a5 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 f2f86deb22c58..ed5e9fb75fbd2 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 b873f4c1353bd..b9ee8f2045503 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 34c804afa329d..13cbc061ccf87 100644 --- a/Telegram/SourceFiles/export/view/export_view_settings.h +++ b/Telegram/SourceFiles/export/view/export_view_settings.h @@ -51,12 +51,12 @@ class SettingsWidget : public Ui::RpWidget { 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);