Add other additional data export.

This commit is contained in:
John Preston 2018-06-24 01:33:47 +01:00
parent 6231db1411
commit 54cab2c5a5
20 changed files with 260 additions and 29 deletions

View File

@ -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_info_about" = "Your chosen screen name, username, phone number and profile pictures.";
"lng_export_option_contacts" = "Contacts list"; "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_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_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_header_chats" = "Chat export settings";
"lng_export_option_personal_chats" = "Personal chats"; "lng_export_option_personal_chats" = "Personal chats";
"lng_export_option_bot_chats" = "Bot 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_total_size" = "Total size: {size}.";
"lng_export_folder" = "Choose export folder"; "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_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_title" = "Data export ready";
"lng_export_suggest_text" = "You can now download the data you requested. Start exporting data?"; "lng_export_suggest_text" = "You can now download the data you requested. Start exporting data?";
"lng_export_suggest_cancel" = "Not now"; "lng_export_suggest_cancel" = "Not now";

View File

@ -192,6 +192,7 @@ inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLo
inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation; inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation;
inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation; inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation;
inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation; inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation;
inputTakeoutFileLocation#29be5899 = InputFileLocation;
inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent; inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent;

View File

@ -71,6 +71,10 @@ Session::Session(not_null<AuthSession*> session)
} }
void Session::startExport() { void Session::startExport() {
if (_exportPanel) {
_exportPanel->activatePanel();
return;
}
_export = std::make_unique<Export::ControllerWrap>(); _export = std::make_unique<Export::ControllerWrap>();
_exportPanel = std::make_unique<Export::View::PanelController>( _exportPanel = std::make_unique<Export::View::PanelController>(
_export.get()); _export.get());

View File

@ -62,6 +62,8 @@ LocationKey ComputeLocationKey(const Data::FileLocation &value) {
}, [&](const MTPDinputEncryptedFileLocation &data) { }, [&](const MTPDinputEncryptedFileLocation &data) {
result.type |= (4ULL << 24); result.type |= (4ULL << 24);
result.id = data.vid.v; result.id = data.vid.v;
}, [&](const MTPDinputTakeoutFileLocation &data) {
result.type |= (5ULL << 24);
}); });
return result; return result;
} }
@ -140,6 +142,11 @@ struct ApiWrap::UserpicsProcess {
int fileIndex = -1; int fileIndex = -1;
}; };
struct ApiWrap::OtherDataProcess {
Data::File file;
FnMut<void(Data::File&&)> done;
};
struct ApiWrap::FileProcess { struct ApiWrap::FileProcess {
FileProcess(const QString &path, Output::Stats *stats); 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) { 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()); Expects(_takeoutId.has_value());
return std::move(_mtp.request(MTPInvokeWithTakeout<MTPupload_GetFile>( return std::move(_mtp.request(MTPInvokeWithTakeout<MTPupload_GetFile>(
@ -269,7 +277,14 @@ auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) {
MTP_int(offset), MTP_int(offset),
MTP_int(kFileChunkSize)) MTP_int(kFileChunkSize))
)).fail([=](RPCError &&result) { )).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))); }).toDC(MTP::ShiftDcId(location.dcId, MTP::kExportMediaDcShift)));
} }
@ -376,6 +391,8 @@ void ApiWrap::requestSplitRanges() {
void ApiWrap::requestDialogsCount() { void ApiWrap::requestDialogsCount() {
Expects(_startProcess != nullptr); Expects(_startProcess != nullptr);
validateSplits();
splitRequest(_startProcess->splitIndex, MTPmessages_GetDialogs( splitRequest(_startProcess->splitIndex, MTPmessages_GetDialogs(
MTP_flags(0), MTP_flags(0),
MTP_int(0), // offset_date MTP_int(0), // offset_date
@ -467,7 +484,7 @@ void ApiWrap::requestDialogsList(
void ApiWrap::validateSplits() { void ApiWrap::validateSplits() {
if (_splits.empty()) { if (_splits.empty()) {
_splits.push_back(MTP_messageRange( _splits.push_back(MTP_messageRange(
MTP_int(0), MTP_int(1),
MTP_int(std::numeric_limits<int>::max()))); MTP_int(std::numeric_limits<int>::max())));
} }
} }
@ -524,6 +541,29 @@ void ApiWrap::requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done) {
}).send(); }).send();
} }
void ApiWrap::requestOtherData(
const QString &suggestedPath,
FnMut<void(Data::File&&)> done) {
Expects(_otherDataProcess == nullptr);
_otherDataProcess = std::make_unique<OtherDataProcess>();
_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( void ApiWrap::requestUserpics(
FnMut<bool(Data::UserpicsInfo&&)> start, FnMut<bool(Data::UserpicsInfo&&)> start,
Fn<bool(DownloadProgress)> progress, Fn<bool(DownloadProgress)> progress,
@ -1185,7 +1225,8 @@ void ApiWrap::loadFile(
Fn<bool(FileProgress)> progress, Fn<bool(FileProgress)> progress,
FnMut<void(QString)> done) { FnMut<void(QString)> done) {
Expects(_fileProcess == nullptr); Expects(_fileProcess == nullptr);
Expects(file.location.dcId != 0); Expects(file.location.dcId != 0
|| file.location.data.type() == mtpc_inputTakeoutFileLocation);
_fileProcess = prepareFileProcess(file); _fileProcess = prepareFileProcess(file);
_fileProcess->progress = std::move(progress); _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."); error("Empty bytes received in file part.");
return; return;
} }
const auto result = _fileProcess->file.writeBlock({});
if (!result) {
ioError(result);
return;
}
} else { } else {
using Request = FileProcess::Request; using Request = FileProcess::Request;
auto &requests = _fileProcess->requests; auto &requests = _fileProcess->requests;

View File

@ -58,6 +58,10 @@ public:
void requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done); void requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done);
void requestOtherData(
const QString &suggestedPath,
FnMut<void(Data::File&&)> done);
struct DownloadProgress { struct DownloadProgress {
QString path; QString path;
int itemIndex = 0; int itemIndex = 0;
@ -91,6 +95,7 @@ private:
struct StartProcess; struct StartProcess;
struct ContactsProcess; struct ContactsProcess;
struct UserpicsProcess; struct UserpicsProcess;
struct OtherDataProcess;
struct FileProcess; struct FileProcess;
struct FileProgress; struct FileProgress;
struct ChatsProcess; struct ChatsProcess;
@ -116,6 +121,8 @@ private:
void finishUserpicsSlice(); void finishUserpicsSlice();
void finishUserpics(); void finishUserpics();
void otherDataDone(const QString &relativePath);
void validateSplits(); void validateSplits();
void requestDialogsSlice(); void requestDialogsSlice();
@ -186,6 +193,7 @@ private:
std::unique_ptr<LoadedFileCache> _fileCache; std::unique_ptr<LoadedFileCache> _fileCache;
std::unique_ptr<ContactsProcess> _contactsProcess; std::unique_ptr<ContactsProcess> _contactsProcess;
std::unique_ptr<UserpicsProcess> _userpicsProcess; std::unique_ptr<UserpicsProcess> _userpicsProcess;
std::unique_ptr<OtherDataProcess> _otherDataProcess;
std::unique_ptr<FileProcess> _fileProcess; std::unique_ptr<FileProcess> _fileProcess;
std::unique_ptr<LeftChannelsProcess> _leftChannelsProcess; std::unique_ptr<LeftChannelsProcess> _leftChannelsProcess;
std::unique_ptr<DialogsProcess> _dialogsProcess; std::unique_ptr<DialogsProcess> _dialogsProcess;

View File

@ -57,6 +57,7 @@ private:
void exportUserpics(); void exportUserpics();
void exportContacts(); void exportContacts();
void exportSessions(); void exportSessions();
void exportOtherData();
void exportDialogs(); void exportDialogs();
void exportNextDialog(); void exportNextDialog();
void exportLeftChannels(); void exportLeftChannels();
@ -73,6 +74,7 @@ private:
ProcessingState stateUserpics(const DownloadProgress &progress) const; ProcessingState stateUserpics(const DownloadProgress &progress) const;
ProcessingState stateContacts() const; ProcessingState stateContacts() const;
ProcessingState stateSessions() const; ProcessingState stateSessions() const;
ProcessingState stateOtherData() const;
ProcessingState stateLeftChannels( ProcessingState stateLeftChannels(
const DownloadProgress &progress) const; const DownloadProgress &progress) const;
ProcessingState stateDialogs(const DownloadProgress &progress) const; ProcessingState stateDialogs(const DownloadProgress &progress) const;
@ -250,6 +252,9 @@ void Controller::fillExportSteps() {
if (_settings.types & Type::Sessions) { if (_settings.types & Type::Sessions) {
_steps.push_back(Step::Sessions); _steps.push_back(Step::Sessions);
} }
if (_settings.types & Type::OtherData) {
_steps.push_back(Step::OtherData);
}
if (_settings.types & Type::AnyChatsMask) { if (_settings.types & Type::AnyChatsMask) {
_steps.push_back(Step::Dialogs); _steps.push_back(Step::Dialogs);
} }
@ -286,6 +291,9 @@ void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
if (_settings.types & Settings::Type::Sessions) { if (_settings.types & Settings::Type::Sessions) {
push(Step::Sessions, 1); push(Step::Sessions, 1);
} }
if (_settings.types & Settings::Type::OtherData) {
push(Step::OtherData, 1);
}
if (_settings.types & Settings::Type::GroupsChannelsMask) { if (_settings.types & Settings::Type::GroupsChannelsMask) {
push(Step::LeftChannels, info.leftChannelsCount); push(Step::LeftChannels, info.leftChannelsCount);
} }
@ -321,6 +329,7 @@ void Controller::exportNext() {
case Step::Userpics: return exportUserpics(); case Step::Userpics: return exportUserpics();
case Step::Contacts: return exportContacts(); case Step::Contacts: return exportContacts();
case Step::Sessions: return exportSessions(); case Step::Sessions: return exportSessions();
case Step::OtherData: return exportOtherData();
case Step::LeftChannels: return exportLeftChannels(); case Step::LeftChannels: return exportLeftChannels();
case Step::Dialogs: return exportDialogs(); case Step::Dialogs: return exportDialogs();
} }
@ -329,7 +338,6 @@ void Controller::exportNext() {
void Controller::initialize() { void Controller::initialize() {
setState(stateInitializing()); setState(stateInitializing());
_api.startExport(_settings, &_stats, [=](ApiWrap::StartInfo info) { _api.startExport(_settings, &_stats, [=](ApiWrap::StartInfo info) {
if (ioCatchError(_writer->start(_settings, &_stats))) { if (ioCatchError(_writer->start(_settings, &_stats))) {
return; return;
@ -340,6 +348,7 @@ void Controller::initialize() {
} }
void Controller::collectLeftChannels() { void Controller::collectLeftChannels() {
setState(stateLeftChannelsList(0));
_api.requestLeftChannelsList([=](int count) { _api.requestLeftChannelsList([=](int count) {
setState(stateLeftChannelsList(count)); setState(stateLeftChannelsList(count));
return true; return true;
@ -350,6 +359,7 @@ void Controller::collectLeftChannels() {
} }
void Controller::collectDialogsList() { void Controller::collectDialogsList() {
setState(stateDialogsList(0));
_api.requestDialogsList([=](int count) { _api.requestDialogsList([=](int count) {
setState(stateDialogsList(count)); setState(stateDialogsList(count));
return true; return true;
@ -360,6 +370,7 @@ void Controller::collectDialogsList() {
} }
void Controller::exportPersonalInfo() { void Controller::exportPersonalInfo() {
setState(statePersonalInfo());
_api.requestPersonalInfo([=](Data::PersonalInfo &&result) { _api.requestPersonalInfo([=](Data::PersonalInfo &&result) {
if (ioCatchError(_writer->writePersonal(result))) { if (ioCatchError(_writer->writePersonal(result))) {
return; return;
@ -395,6 +406,7 @@ void Controller::exportUserpics() {
} }
void Controller::exportContacts() { void Controller::exportContacts() {
setState(stateContacts());
_api.requestContacts([=](Data::ContactsList &&result) { _api.requestContacts([=](Data::ContactsList &&result) {
if (ioCatchError(_writer->writeContactsList(result))) { if (ioCatchError(_writer->writeContactsList(result))) {
return; return;
@ -404,6 +416,7 @@ void Controller::exportContacts() {
} }
void Controller::exportSessions() { void Controller::exportSessions() {
setState(stateSessions());
_api.requestSessions([=](Data::SessionsList &&result) { _api.requestSessions([=](Data::SessionsList &&result) {
if (ioCatchError(_writer->writeSessionsList(result))) { if (ioCatchError(_writer->writeSessionsList(result))) {
return; 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() { void Controller::exportDialogs() {
if (ioCatchError(_writer->writeDialogsStart(_dialogsInfo))) { if (ioCatchError(_writer->writeDialogsStart(_dialogsInfo))) {
return; return;
@ -573,6 +597,10 @@ ProcessingState Controller::stateSessions() const {
return prepareState(Step::Sessions); return prepareState(Step::Sessions);
} }
ProcessingState Controller::stateOtherData() const {
return prepareState(Step::OtherData);
}
ProcessingState Controller::stateLeftChannels( ProcessingState Controller::stateLeftChannels(
const DownloadProgress & progress) const { const DownloadProgress & progress) const {
const auto step = Step::LeftChannels; const auto step = Step::LeftChannels;

View File

@ -33,6 +33,7 @@ struct ProcessingState {
Userpics, Userpics,
Contacts, Contacts,
Sessions, Sessions,
OtherData,
LeftChannels, LeftChannels,
Dialogs, Dialogs,
}; };

View File

@ -37,7 +37,7 @@ bool Settings::validate() const {
return false; return false;
} else if ((fullChats & MustNotBeFull) != 0) { } else if ((fullChats & MustNotBeFull) != 0) {
return false; return false;
} else if (format != Format::Text && format != Format::Json) { } else if (format != Format::Html && format != Format::Json) {
return false; return false;
} else if (!media.validate()) { } else if (!media.validate()) {
return false; return false;

View File

@ -50,12 +50,13 @@ struct Settings {
Userpics = 0x002, Userpics = 0x002,
Contacts = 0x004, Contacts = 0x004,
Sessions = 0x008, Sessions = 0x008,
PersonalChats = 0x010, OtherData = 0x010,
BotChats = 0x020, PersonalChats = 0x020,
PrivateGroups = 0x040, BotChats = 0x040,
PublicGroups = 0x080, PrivateGroups = 0x080,
PrivateChannels = 0x100, PublicGroups = 0x100,
PublicChannels = 0x200, PrivateChannels = 0x200,
PublicChannels = 0x400,
GroupsMask = PrivateGroups | PublicGroups, GroupsMask = PrivateGroups | PublicGroups,
ChannelsMask = PrivateChannels | PublicChannels, ChannelsMask = PrivateChannels | PublicChannels,
@ -63,7 +64,7 @@ struct Settings {
NonChannelChatsMask = PersonalChats | BotChats | PrivateGroups, NonChannelChatsMask = PersonalChats | BotChats | PrivateGroups,
AnyChatsMask = PersonalChats | BotChats | GroupsChannelsMask, AnyChatsMask = PersonalChats | BotChats | GroupsChannelsMask,
NonChatsMask = PersonalInfo | Userpics | Contacts | Sessions, NonChatsMask = PersonalInfo | Userpics | Contacts | Sessions,
AllMask = NonChatsMask | AnyChatsMask, AllMask = NonChatsMask | OtherData | AnyChatsMask,
}; };
using Types = base::flags<Type>; using Types = base::flags<Type>;
friend inline constexpr auto is_flag_type(Type) { return true; }; friend inline constexpr auto is_flag_type(Type) { return true; };

View File

@ -19,6 +19,7 @@ struct SessionsList;
struct DialogsInfo; struct DialogsInfo;
struct DialogInfo; struct DialogInfo;
struct MessagesSlice; struct MessagesSlice;
struct File;
} // namespace Data } // namespace Data
struct Settings; struct Settings;
@ -60,6 +61,9 @@ public:
[[nodiscard]] virtual Result writeSessionsList( [[nodiscard]] virtual Result writeSessionsList(
const Data::SessionsList &data) = 0; const Data::SessionsList &data) = 0;
[[nodiscard]] virtual Result writeOtherData(
const Data::File &data) = 0;
[[nodiscard]] virtual Result writeDialogsStart( [[nodiscard]] virtual Result writeDialogsStart(
const Data::DialogsInfo &data) = 0; const Data::DialogsInfo &data) = 0;
[[nodiscard]] virtual Result writeDialogStart( [[nodiscard]] virtual Result writeDialogStart(

View File

@ -637,7 +637,7 @@ QByteArray HtmlWriter::Wrap::end() const {
} }
HtmlWriter::Wrap::~Wrap() { HtmlWriter::Wrap::~Wrap() {
Expects(_file.empty() || _closed); (void)close();
} }
HtmlWriter::HtmlWriter() = default; HtmlWriter::HtmlWriter() = default;
@ -1019,6 +1019,17 @@ Result HtmlWriter::writeWebSessions(const Data::SessionsList &data) {
return _summary->writeBlock(header); 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) { Result HtmlWriter::writeDialogsStart(const Data::DialogsInfo &data) {
return writeChatsStart( return writeChatsStart(
data, data,
@ -1123,7 +1134,9 @@ Result HtmlWriter::writeChatSlice(const Data::MessagesSlice &data) {
data.peers, data.peers,
_settings.internalLinksDomain)); _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); return _chat->writeBlock(full);
} }
@ -1172,7 +1185,7 @@ Result HtmlWriter::writeChatEnd() {
} }
Unexpected("Dialog type in TypeString."); Unexpected("Dialog type in TypeString.");
}; };
return _chats->writeBlock(SerializeKeyValue({ return _chats->writeBlock(kLineBreak + SerializeKeyValue({
{ "Name", SerializeString(NameString(_dialog, _dialog.type)) }, { "Name", SerializeString(NameString(_dialog, _dialog.type)) },
{ "Type", SerializeString(TypeString(_dialog.type)) }, { "Type", SerializeString(TypeString(_dialog.type)) },
{ {
@ -1190,7 +1203,7 @@ Result HtmlWriter::writeChatEnd() {
(_dialog.relativePath + "messages.html"))) (_dialog.relativePath + "messages.html")))
: QByteArray()) : QByteArray())
} }
}) + kLineBreak); }));
} }
Result HtmlWriter::writeChatsEnd() { Result HtmlWriter::writeChatsEnd() {

View File

@ -35,6 +35,8 @@ public:
Result writeSessionsList(const Data::SessionsList &data) override; Result writeSessionsList(const Data::SessionsList &data) override;
Result writeOtherData(const Data::File &data) override;
Result writeDialogsStart(const Data::DialogsInfo &data) override; Result writeDialogsStart(const Data::DialogsInfo &data) override;
Result writeDialogStart(const Data::DialogInfo &data) override; Result writeDialogStart(const Data::DialogInfo &data) override;
Result writeDialogSlice(const Data::MessagesSlice &data) override; Result writeDialogSlice(const Data::MessagesSlice &data) override;

View File

@ -13,6 +13,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/utils.h" #include "core/utils.h"
#include <QtCore/QDateTime> #include <QtCore/QDateTime>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonValue>
namespace Export { namespace Export {
namespace Output { namespace Output {
@ -793,6 +797,77 @@ Result JsonWriter::writeSessionsList(const Data::SessionsList &data) {
return Result::Success(); 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<void(const QJsonObject &data)> pushObject;
Fn<void(const QJsonArray &data)> pushArray;
Fn<void(const QJsonValue &data)> 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) { Result JsonWriter::writeSessions(const Data::SessionsList &data) {
Expects(_output != nullptr); Expects(_output != nullptr);

View File

@ -45,6 +45,8 @@ public:
Result writeSessionsList(const Data::SessionsList &data) override; Result writeSessionsList(const Data::SessionsList &data) override;
Result writeOtherData(const Data::File &data) override;
Result writeDialogsStart(const Data::DialogsInfo &data) override; Result writeDialogsStart(const Data::DialogsInfo &data) override;
Result writeDialogStart(const Data::DialogInfo &data) override; Result writeDialogStart(const Data::DialogInfo &data) override;
Result writeDialogSlice(const Data::MessagesSlice &data) override; Result writeDialogSlice(const Data::MessagesSlice &data) override;

View File

@ -762,6 +762,15 @@ Result TextWriter::writeWebSessions(const Data::SessionsList &data) {
return _summary->writeBlock(header); 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) { Result TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
return writeChatsStart( return writeChatsStart(
data, data,
@ -864,7 +873,9 @@ Result TextWriter::writeChatSlice(const Data::MessagesSlice &data) {
data.peers, data.peers,
_settings.internalLinksDomain)); _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); return _chat->writeBlock(full);
} }
@ -911,7 +922,7 @@ Result TextWriter::writeChatEnd() {
} }
Unexpected("Dialog type in TypeString."); Unexpected("Dialog type in TypeString.");
}; };
return _chats->writeBlock(SerializeKeyValue({ return _chats->writeBlock(kLineBreak + SerializeKeyValue({
{ "Name", NameString(_dialog, _dialog.type) }, { "Name", NameString(_dialog, _dialog.type) },
{ "Type", TypeString(_dialog.type) }, { "Type", TypeString(_dialog.type) },
{ {
@ -926,7 +937,7 @@ Result TextWriter::writeChatEnd() {
? (_dialog.relativePath + "messages.txt").toUtf8() ? (_dialog.relativePath + "messages.txt").toUtf8()
: QByteArray()) : QByteArray())
} }
}) + kLineBreak); }));
} }
Result TextWriter::writeChatsEnd() { Result TextWriter::writeChatsEnd() {

View File

@ -33,6 +33,8 @@ public:
Result writeSessionsList(const Data::SessionsList &data) override; Result writeSessionsList(const Data::SessionsList &data) override;
Result writeOtherData(const Data::File &data) override;
Result writeDialogsStart(const Data::DialogsInfo &data) override; Result writeDialogsStart(const Data::DialogsInfo &data) override;
Result writeDialogStart(const Data::DialogInfo &data) override; Result writeDialogStart(const Data::DialogInfo &data) override;
Result writeDialogSlice(const Data::MessagesSlice &data) override; Result writeDialogSlice(const Data::MessagesSlice &data) override;

View File

@ -80,6 +80,9 @@ Content ContentFromState(const ProcessingState &state) {
case Step::Sessions: case Step::Sessions:
pushMain(lang(lng_export_option_sessions)); pushMain(lang(lng_export_option_sessions));
break; break;
case Step::OtherData:
pushMain(lang(lng_export_option_other));
break;
case Step::LeftChannels: case Step::LeftChannels:
case Step::Dialogs: case Step::Dialogs:
pushMain(lang(lng_export_state_chats)); pushMain(lang(lng_export_state_chats));

View File

@ -91,8 +91,10 @@ void SuggestStart() {
void ClearSuggestStart() { void ClearSuggestStart() {
auto settings = Local::ReadExportSettings(); auto settings = Local::ReadExportSettings();
settings.availableAt = 0; if (settings.availableAt) {
Local::WriteExportSettings(settings); settings.availableAt = 0;
Local::WriteExportSettings(settings);
}
} }
PanelController::PanelController(not_null<ControllerWrap*> process) PanelController::PanelController(not_null<ControllerWrap*> process)
@ -160,7 +162,18 @@ void PanelController::showError(const ApiErrorState &error) {
qstr("TAKEOUT_INIT_DELAY_").size()).toInt(), 1); qstr("TAKEOUT_INIT_DELAY_").size()).toInt(), 1);
const auto now = QDateTime::currentDateTime(); const auto now = QDateTime::currentDateTime();
const auto when = now.addSecs(seconds); 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; _settings->availableAt = unixtime() + seconds;
_saveSettingsTimer.callOnce(kSaveSettingsTimeout); _saveSettingsTimer.callOnce(kSaveSettingsTimeout);
@ -217,6 +230,9 @@ void PanelController::showError(const QString &text) {
} }
void PanelController::showProgress() { void PanelController::showProgress() {
_settings->availableAt = 0;
ClearSuggestStart();
_panel->setTitle(Lang::Viewer(lng_export_progress_title)); _panel->setTitle(Lang::Viewer(lng_export_progress_title));
auto progress = base::make_unique_q<ProgressWidget>( auto progress = base::make_unique_q<ProgressWidget>(

View File

@ -134,6 +134,13 @@ void SettingsWidget::setupOptions(not_null<Ui::VerticalLayout*> container) {
Type::PublicChannels); Type::PublicChannels);
setupMediaOptions(container); setupMediaOptions(container);
addHeader(container, lng_export_header_other);
addOptionWithAbout(
container,
lng_export_option_other,
Type::OtherData,
lng_export_option_other_about);
} }
void SettingsWidget::setupMediaOptions( void SettingsWidget::setupMediaOptions(
@ -320,12 +327,12 @@ not_null<Ui::Checkbox*> SettingsWidget::addOption(
return checkbox; return checkbox;
} }
void SettingsWidget::addOptionWithAbout( not_null<Ui::Checkbox*> SettingsWidget::addOptionWithAbout(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
LangKey key, LangKey key,
Types types, Types types,
LangKey about) { LangKey about) {
addOption(container, key, types); const auto result = addOption(container, key, types);
const auto label = container->add( const auto label = container->add(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
container, container,
@ -333,6 +340,7 @@ void SettingsWidget::addOptionWithAbout(
Ui::FlatLabel::InitType::Simple, Ui::FlatLabel::InitType::Simple,
st::exportAboutOptionLabel), st::exportAboutOptionLabel),
st::exportAboutOptionPadding); st::exportAboutOptionPadding);
return result;
} }
void SettingsWidget::addChatOption( void SettingsWidget::addChatOption(

View File

@ -51,12 +51,12 @@ private:
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
LangKey key, LangKey key,
Types types); Types types);
void addOptionWithAbout( not_null<Ui::Checkbox*> addOptionWithAbout(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
LangKey key, LangKey key,
Types types, Types types,
LangKey about); LangKey about);
void addChatOption( void addChatOption(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
LangKey key, LangKey key,
Types types); Types types);