Allow single chat history export.

This commit is contained in:
John Preston 2018-07-23 16:11:56 +03:00
parent 6429e8b532
commit a99ae76ad4
22 changed files with 424 additions and 62 deletions

View file

@ -672,6 +672,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_delete_conversation" = "Delete conversation"; "lng_profile_delete_conversation" = "Delete conversation";
"lng_profile_block_user" = "Block user"; "lng_profile_block_user" = "Block user";
"lng_profile_unblock_user" = "Unblock user"; "lng_profile_unblock_user" = "Unblock user";
"lng_profile_export_chat" = "Export chat history";
"lng_profile_export_channel" = "Export channel history";
"lng_media_selected_photo#one" = "{count} Photo"; "lng_media_selected_photo#one" = "{count} Photo";
"lng_media_selected_photo#other" = "{count} Photos"; "lng_media_selected_photo#other" = "{count} Photos";
"lng_media_selected_video#one" = "{count} Video"; "lng_media_selected_video#one" = "{count} Video";

View file

@ -72,12 +72,16 @@ Session::Session(not_null<AuthSession*> session)
setupChannelLeavingViewer(); setupChannelLeavingViewer();
} }
void Session::startExport() { void Session::startExport(PeerData *peer) {
startExport(peer ? peer->input : MTP_inputPeerEmpty());
}
void Session::startExport(const MTPInputPeer &singlePeer) {
if (_exportPanel) { if (_exportPanel) {
_exportPanel->activatePanel(); _exportPanel->activatePanel();
return; return;
} }
_export = std::make_unique<Export::ControllerWrap>(); _export = std::make_unique<Export::ControllerWrap>(singlePeer);
_exportPanel = std::make_unique<Export::View::PanelController>( _exportPanel = std::make_unique<Export::View::PanelController>(
_export.get()); _export.get());

View file

@ -56,7 +56,8 @@ public:
return *_session; return *_session;
} }
void startExport(); void startExport(PeerData *peer = nullptr);
void startExport(const MTPInputPeer &singlePeer);
void suggestStartExport(TimeId availableAt); void suggestStartExport(TimeId availableAt);
void clearExportSuggestion(); void clearExportSuggestion();
rpl::producer<Export::View::PanelController*> currentExportView() const; rpl::producer<Export::View::PanelController*> currentExportView() const;

View file

@ -1386,19 +1386,36 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
return result; return result;
} }
DialogInfo DialogInfoFromUser(const User &data) {
auto result = DialogInfo();
result.input = (Peer{ data }).input();
result.name = data.info.firstName;
result.lastName = data.info.lastName;
result.peerId = UserPeerId(data.info.userId);
result.topMessageDate = 0;
result.topMessageId = 0;
result.type = DialogTypeFromUser(data);
result.isLeftChannel = false;
return result;
}
DialogInfo DialogInfoFromChat(const Chat &data) {
auto result = DialogInfo();
result.input = data.input;
result.name = data.title;
result.peerId = ChatPeerId(data.id);
result.topMessageDate = 0;
result.topMessageId = 0;
result.type = DialogTypeFromChat(data);
return result;
}
DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data) { DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data) {
auto result = DialogsInfo(); auto result = DialogsInfo();
data.match([&](const auto &data) { //MTPDmessages_chats &data) { data.match([&](const auto &data) { //MTPDmessages_chats &data) {
result.left.reserve(data.vchats.v.size()); result.left.reserve(data.vchats.v.size());
for (const auto &single : data.vchats.v) { for (const auto &single : data.vchats.v) {
const auto chat = ParseChat(single); auto info = DialogInfoFromChat(ParseChat(single));
auto info = DialogInfo();
info.input = chat.input;
info.name = chat.title;
info.peerId = ChatPeerId(chat.id);
info.topMessageDate = 0;
info.topMessageId = 0;
info.type = DialogTypeFromChat(chat);
info.isLeftChannel = true; info.isLeftChannel = true;
result.left.push_back(std::move(info)); result.left.push_back(std::move(info));
} }
@ -1406,6 +1423,65 @@ DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data) {
return result; return result;
} }
DialogsInfo ParseDialogsInfo(
const MTPInputPeer &singlePeer,
const MTPVector<MTPUser> &data) {
const auto singleId = singlePeer.match(
[](const MTPDinputPeerUser &data) {
return data.vuser_id.v;
}, [](const MTPDinputPeerSelf &data) {
return 0;
}, [](const auto &data) -> int {
Unexpected("Single peer type in ParseDialogsInfo(users).");
});
auto result = DialogsInfo();
result.chats.reserve(data.v.size());
for (const auto &single : data.v) {
const auto userId = single.match([&](const auto &data) {
return data.vid.v;
});
if (userId != singleId
&& (singleId != 0
|| single.type() != mtpc_user
|| !single.c_user().is_self())) {
continue;
}
auto info = DialogInfoFromUser(ParseUser(single));
result.chats.push_back(std::move(info));
}
return result;
}
DialogsInfo ParseDialogsInfo(
const MTPInputPeer &singlePeer,
const MTPmessages_Chats &data) {
const auto singleId = singlePeer.match(
[](const MTPDinputPeerChat &data) {
return data.vchat_id.v;
}, [](const MTPDinputPeerChannel &data) {
return data.vchannel_id.v;
}, [](const auto &data) -> int {
Unexpected("Single peer type in ParseDialogsInfo(chats).");
});
auto result = DialogsInfo();
data.match([&](const auto &data) { //MTPDmessages_chats &data) {
result.chats.reserve(data.vchats.v.size());
for (const auto &single : data.vchats.v) {
const auto chatId = single.match([&](const auto &data) {
return data.vid.v;
});
if (chatId != singleId) {
continue;
}
const auto chat = ParseChat(single);
auto info = DialogInfoFromChat(ParseChat(single));
info.isLeftChannel = false;
result.chats.push_back(std::move(info));
}
});
return result;
}
void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) { void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
auto &chats = info.chats; auto &chats = info.chats;
auto &left = info.left; auto &left = info.left;
@ -1414,7 +1490,9 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
auto index = 0; auto index = 0;
for (auto &dialog : chats) { for (auto &dialog : chats) {
const auto number = Data::NumberToString(++index, digits, '0'); const auto number = Data::NumberToString(++index, digits, '0');
dialog.relativePath = "chats/chat_" + number + '/'; dialog.relativePath = settings.onlySinglePeer()
? QString()
: "chats/chat_" + QString::fromUtf8(number) + '/';
using DialogType = DialogInfo::Type; using DialogType = DialogInfo::Type;
using Type = Settings::Type; using Type = Settings::Type;
@ -1436,6 +1514,8 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
ranges::reverse(dialog.splits); ranges::reverse(dialog.splits);
} }
for (auto &dialog : left) { for (auto &dialog : left) {
Assert(!settings.onlySinglePeer());
const auto number = Data::NumberToString(++index, digits, '0'); const auto number = Data::NumberToString(++index, digits, '0');
dialog.relativePath = "chats/chat_" + number + '/'; dialog.relativePath = "chats/chat_" + number + '/';
dialog.onlyMyMessages = true; dialog.onlyMyMessages = true;

View file

@ -546,6 +546,12 @@ struct DialogsInfo {
DialogInfo::Type DialogTypeFromChat(const Chat &chat); DialogInfo::Type DialogTypeFromChat(const Chat &chat);
DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data); DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data);
DialogsInfo ParseDialogsInfo(
const MTPInputPeer &singlePeer,
const MTPVector<MTPUser> &data);
DialogsInfo ParseDialogsInfo(
const MTPInputPeer &singlePeer,
const MTPmessages_Chats &data);
DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data); DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data);
void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings); void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings);

View file

@ -369,7 +369,8 @@ auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) {
filePartDone(0, MTP_upload_file(MTP_storage_filePartial(), filePartDone(0, MTP_upload_file(MTP_storage_filePartial(),
MTP_int(0), MTP_int(0),
MTP_bytes(QByteArray()))); MTP_bytes(QByteArray())));
} else if (result.type() == qstr("LOCATION_INVALID")) { } else if (result.type() == qstr("LOCATION_INVALID")
|| result.type() == qstr("VERSION_INVALID")) {
filePartUnavailable(); filePartUnavailable();
} else { } else {
error(std::move(result)); error(std::move(result));
@ -413,7 +414,9 @@ void ApiWrap::startExport(
_startProcess->steps.push_back(Step::DialogsCount); _startProcess->steps.push_back(Step::DialogsCount);
} }
if (_settings->types & Settings::Type::GroupsChannelsMask) { if (_settings->types & Settings::Type::GroupsChannelsMask) {
_startProcess->steps.push_back(Step::LeftChannelsCount); if (!_settings->onlySinglePeer()) {
_startProcess->steps.push_back(Step::LeftChannelsCount);
}
} }
startMainSession([=] { startMainSession([=] {
sendNextStartRequest(); sendNextStartRequest();
@ -489,6 +492,15 @@ void ApiWrap::requestSplitRanges() {
void ApiWrap::requestDialogsCount() { void ApiWrap::requestDialogsCount() {
Expects(_startProcess != nullptr); Expects(_startProcess != nullptr);
if (_settings->onlySinglePeer()) {
_startProcess->info.dialogsCount =
(_settings->singlePeer.type() == mtpc_inputPeerChannel
? 1
: _splits.size());
sendNextStartRequest();
return;
}
const auto offsetDate = 0; const auto offsetDate = 0;
const auto offsetId = 0; const auto offsetId = 0;
const auto offsetPeer = MTP_inputPeerEmpty(); const auto offsetPeer = MTP_inputPeerEmpty();
@ -959,9 +971,58 @@ void ApiWrap::cancelExportFast() {
} }
} }
void ApiWrap::requestSinglePeerDialog() {
auto doneSinglePeer = [=](const auto &result) {
auto info = Data::ParseDialogsInfo(_settings->singlePeer, result);
_dialogsProcess->processedCount += info.chats.size();
appendDialogsSlice(std::move(info));
const auto last = _dialogsProcess->splitIndexPlusOne - 1;
for (auto &info : _dialogsProcess->info.chats) {
for (auto i = last; i != 0; --i) {
info.splits.push_back(i - 1);
info.messagesCountPerSplit.push_back(0);
}
}
if (!_dialogsProcess->progress(_dialogsProcess->processedCount)) {
return;
}
finishDialogsList();
};
const auto requestUser = [&](const MTPInputUser &data) {
mainRequest(MTPusers_GetUsers(
MTP_vector<MTPInputUser>(1, data)
)).done(std::move(doneSinglePeer)).send();
};
_settings->singlePeer.match([&](const MTPDinputPeerUser &data) {
requestUser(MTP_inputUser(data.vuser_id, data.vaccess_hash));
}, [&](const MTPDinputPeerChat &data) {
mainRequest(MTPmessages_GetChats(
MTP_vector<MTPint>(1, data.vchat_id)
)).done(std::move(doneSinglePeer)).send();
}, [&](const MTPDinputPeerChannel &data) {
mainRequest(MTPchannels_GetChannels(
MTP_vector<MTPInputChannel>(
1,
MTP_inputChannel(data.vchannel_id, data.vaccess_hash))
)).done(std::move(doneSinglePeer)).send();
}, [&](const MTPDinputPeerSelf &data) {
requestUser(MTP_inputUserSelf());
}, [](const MTPDinputPeerEmpty &data) {
Unexpected("Empty peer in ApiWrap::requestSinglePeerDialog.");
});
}
void ApiWrap::requestDialogsSlice() { void ApiWrap::requestDialogsSlice() {
Expects(_dialogsProcess != nullptr); Expects(_dialogsProcess != nullptr);
if (_settings->onlySinglePeer()) {
requestSinglePeerDialog();
return;
}
const auto splitIndex = _dialogsProcess->splitIndexPlusOne - 1; const auto splitIndex = _dialogsProcess->splitIndexPlusOne - 1;
const auto hash = 0; const auto hash = 0;
splitRequest(splitIndex, MTPmessages_GetDialogs( splitRequest(splitIndex, MTPmessages_GetDialogs(

View file

@ -124,6 +124,7 @@ private:
void requestDialogsSlice(); void requestDialogsSlice();
void appendDialogsSlice(Data::DialogsInfo &&info); void appendDialogsSlice(Data::DialogsInfo &&info);
void finishDialogsList(); void finishDialogsList();
void requestSinglePeerDialog();
void requestLeftChannelsIfNeeded(); void requestLeftChannelsIfNeeded();
void requestLeftChannelsList( void requestLeftChannelsList(

View file

@ -15,12 +15,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/output/export_output_stats.h" #include "export/output/export_output_stats.h"
namespace Export { namespace Export {
namespace {
auto kNullStateCallback = [](ProcessingState&) {}; const auto kNullStateCallback = [](ProcessingState&) {};
Settings NormalizeSettings(const Settings &settings) {
if (!settings.onlySinglePeer()) {
return base::duplicate(settings);
}
auto result = base::duplicate(settings);
result.format = Output::Format::Html;
result.types = result.fullChats = Settings::Type::AnyChatsMask;
return result;
}
} // namespace
class Controller { class Controller {
public: public:
Controller(crl::weak_on_queue<Controller> weak); Controller(
crl::weak_on_queue<Controller> weak,
const MTPInputPeer &peer);
rpl::producer<State> state() const; rpl::producer<State> state() const;
@ -83,8 +98,6 @@ private:
int substepsInStep(Step step) const; int substepsInStep(Step step) const;
bool normalizePath();
ApiWrap _api; ApiWrap _api;
Settings _settings; Settings _settings;
Environment _environment; Environment _environment;
@ -117,7 +130,9 @@ private:
}; };
Controller::Controller(crl::weak_on_queue<Controller> weak) Controller::Controller(
crl::weak_on_queue<Controller> weak,
const MTPInputPeer &peer)
: _api(weak.runner()) : _api(weak.runner())
, _state(PasswordCheckState{}) { , _state(PasswordCheckState{}) {
_api.errors( _api.errors(
@ -134,6 +149,7 @@ Controller::Controller(crl::weak_on_queue<Controller> weak)
auto state = PasswordCheckState(); auto state = PasswordCheckState();
state.checked = false; state.checked = false;
state.requesting = false; state.requesting = false;
state.singlePeer = peer;
setState(std::move(state)); setState(std::move(state));
} }
@ -218,10 +234,10 @@ void Controller::startExport(
if (!_settings.path.isEmpty()) { if (!_settings.path.isEmpty()) {
return; return;
} }
_settings = base::duplicate(settings); _settings = NormalizeSettings(settings);
_environment = environment; _environment = environment;
_settings.path = Output::NormalizePath(_settings.path); _settings.path = Output::NormalizePath(_settings);
_writer = Output::CreateWriter(_settings.format); _writer = Output::CreateWriter(_settings.format);
fillExportSteps(); fillExportSteps();
exportNext(); exportNext();
@ -569,7 +585,7 @@ void Controller::setFinishedState() {
_stats.bytesCount() }); _stats.bytesCount() });
} }
ControllerWrap::ControllerWrap() { ControllerWrap::ControllerWrap(const MTPInputPeer &peer) : _wrapped(peer) {
} }
rpl::producer<State> ControllerWrap::state() const { rpl::producer<State> ControllerWrap::state() const {

View file

@ -23,6 +23,7 @@ struct PasswordCheckState {
bool requesting = true; bool requesting = true;
bool hasPassword = false; bool hasPassword = false;
bool checked = false; bool checked = false;
MTPInputPeer singlePeer = MTP_inputPeerEmpty();
}; };
struct ProcessingState { struct ProcessingState {
@ -110,7 +111,7 @@ using State = base::optional_variant<
class ControllerWrap { class ControllerWrap {
public: public:
ControllerWrap(); explicit ControllerWrap(const MTPInputPeer &peer);
rpl::producer<State> state() const; rpl::producer<State> state() const;

View file

@ -76,8 +76,14 @@ struct Settings {
Types fullChats = DefaultFullChats(); Types fullChats = DefaultFullChats();
MediaSettings media; MediaSettings media;
MTPInputPeer singlePeer = MTP_inputPeerEmpty();
TimeId availableAt = 0; TimeId availableAt = 0;
bool onlySinglePeer() const {
return singlePeer.type() != mtpc_inputPeerEmpty;
}
static inline Types DefaultTypes() { static inline Types DefaultTypes() {
return Type::PersonalInfo return Type::PersonalInfo
| Type::Userpics | Type::Userpics

View file

@ -19,8 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export { namespace Export {
namespace Output { namespace Output {
QString NormalizePath(const QString &source) { QString NormalizePath(const Settings &settings) {
QDir folder(source); QDir folder(settings.path);
const auto path = folder.absolutePath(); const auto path = folder.absolutePath();
auto result = path.endsWith('/') ? path : (path + '/'); auto result = path.endsWith('/') ? path : (path + '/');
if (!folder.exists()) { if (!folder.exists()) {
@ -32,7 +32,9 @@ QString NormalizePath(const QString &source) {
return result; return result;
} }
const auto date = QDate::currentDate(); const auto date = QDate::currentDate();
const auto base = QString("DataExport_%1_%2_%3" const auto base = QString(settings.onlySinglePeer()
? "ChatExport_%1_%2_%3"
: "DataExport_%1_%2_%3"
).arg(date.day(), 2, 10, QChar('0') ).arg(date.day(), 2, 10, QChar('0')
).arg(date.month(), 2, 10, QChar('0') ).arg(date.month(), 2, 10, QChar('0')
).arg(date.year()); ).arg(date.year());

View file

@ -27,7 +27,7 @@ struct Environment;
namespace Output { namespace Output {
QString NormalizePath(const QString &source); QString NormalizePath(const Settings &settings);
struct Result; struct Result;
class Stats; class Stats;

View file

@ -1727,7 +1727,6 @@ Result HtmlWriter::start(
_settings = base::duplicate(settings); _settings = base::duplicate(settings);
_environment = environment; _environment = environment;
_stats = stats; _stats = stats;
_summary = fileWithRelativePath(mainFileRelativePath());
//const auto result = copyFile( //const auto result = copyFile(
// ":/export/css/bootstrap.min.css", // ":/export/css/bootstrap.min.css",
@ -1771,6 +1770,11 @@ Result HtmlWriter::start(
} }
} }
} }
if (_settings.onlySinglePeer()) {
return Result::Success();
}
_summary = fileWithRelativePath(mainFileRelativePath());
auto block = _summary->pushHeader("Exported Data"); auto block = _summary->pushHeader("Exported Data");
block.append(_summary->pushDiv("page_body")); block.append(_summary->pushDiv("page_body"));
return _summary->writeBlock(block); return _summary->writeBlock(block);
@ -1810,6 +1814,8 @@ Result HtmlWriter::writeDelayedPersonal(const QString &userpicPath) {
Result HtmlWriter::writePreparedPersonal( Result HtmlWriter::writePreparedPersonal(
const Data::PersonalInfo &data, const Data::PersonalInfo &data,
const QString &userpicPath) { const QString &userpicPath) {
Expects(_summary != nullptr);
const auto &info = data.user.info; const auto &info = data.user.info;
auto userpic = UserpicData{ _selfColorIndex, kPersonalUserpicSize }; auto userpic = UserpicData{ _selfColorIndex, kPersonalUserpicSize };
@ -2197,11 +2203,12 @@ Result HtmlWriter::writeOtherData(const Data::File &data) {
} }
Result HtmlWriter::writeDialogsStart(const Data::DialogsInfo &data) { Result HtmlWriter::writeDialogsStart(const Data::DialogsInfo &data) {
Expects(_summary != nullptr);
Expects(_chats == nullptr); Expects(_chats == nullptr);
if (data.chats.empty() && data.left.empty()) { if (data.chats.empty() && data.left.empty()) {
return Result::Success(); return Result::Success();
} else if (_settings.onlySinglePeer()) {
return Result::Success();
} }
_dialogsRelativePath = "lists/chats.html"; _dialogsRelativePath = "lists/chats.html";
@ -2293,12 +2300,35 @@ Result HtmlWriter::writeDialogSlice(const Data::MessagesSlice &data) {
return _chat->writeBlock(block); return _chat->writeBlock(block);
} }
Result HtmlWriter::writeDialogEnd() { Result HtmlWriter::writeEmptySinglePeer() {
Expects(_chats != nullptr);
Expects(_chat != nullptr); Expects(_chat != nullptr);
if (!_settings.onlySinglePeer() || _messagesCount != 0) {
return Result::Success();
}
Assert(_chatFileEmpty);
if (const auto result = writeDialogOpening(0); !result) {
return result;
}
return _chat->writeBlock(_chat->pushServiceMessage(
--_dateMessageId,
_dialog,
_settings.path,
"Empty chat"));
}
Result HtmlWriter::writeDialogEnd() {
Expects(_settings.onlySinglePeer() || _chats != nullptr);
Expects(_chat != nullptr);
if (const auto result = writeEmptySinglePeer(); !result) {
return result;
}
if (const auto closed = base::take(_chat)->close(); !closed) { if (const auto closed = base::take(_chat)->close(); !closed) {
return closed; return closed;
} else if (_settings.onlySinglePeer()) {
return Result::Success();
} }
using Type = Data::DialogInfo::Type; using Type = Data::DialogInfo::Type;
@ -2411,7 +2441,7 @@ Result HtmlWriter::writeDialogOpening(int index) {
: (_dialog.name + ' ' + _dialog.lastName); : (_dialog.name + ' ' + _dialog.lastName);
auto block = _chat->pushHeader( auto block = _chat->pushHeader(
name, name,
_dialogsRelativePath); _settings.onlySinglePeer() ? QString() : _dialogsRelativePath);
block.append(_chat->pushDiv("page_body chat_page")); block.append(_chat->pushDiv("page_body chat_page"));
block.append(_chat->pushDiv("history")); block.append(_chat->pushDiv("history"));
if (index > 0) { if (index > 0) {
@ -2489,7 +2519,11 @@ Result HtmlWriter::switchToNextChatFile(int index) {
} }
Result HtmlWriter::finish() { Result HtmlWriter::finish() {
Expects(_summary != nullptr); Expects(_settings.onlySinglePeer() || _summary != nullptr);
if (_settings.onlySinglePeer()) {
return Result::Success();
}
auto block = QByteArray(); auto block = QByteArray();
if (_haveSections) { if (_haveSections) {
@ -2516,7 +2550,9 @@ Result HtmlWriter::copyFile(
} }
QString HtmlWriter::mainFilePath() { QString HtmlWriter::mainFilePath() {
return pathWithRelativePath(mainFileRelativePath()); return pathWithRelativePath(_settings.onlySinglePeer()
? messagesFile(0)
: mainFileRelativePath());
} }
QString HtmlWriter::mainFileRelativePath() const { QString HtmlWriter::mainFileRelativePath() const {

View file

@ -108,6 +108,7 @@ private:
[[nodiscard]] Result validateDialogsMode(bool isLeftChannel); [[nodiscard]] Result validateDialogsMode(bool isLeftChannel);
[[nodiscard]] Result writeDialogOpening(int index); [[nodiscard]] Result writeDialogOpening(int index);
[[nodiscard]] Result switchToNextChatFile(int index); [[nodiscard]] Result switchToNextChatFile(int index);
[[nodiscard]] Result writeEmptySinglePeer();
void pushSection( void pushSection(
int priority, int priority,

View file

@ -45,7 +45,7 @@ void SuggestBox::prepare() {
addButton(langFactory(lng_box_ok), [=] { addButton(langFactory(lng_box_ok), [=] {
closeBox(); closeBox();
Auth().data().startExport(); Auth().data().startExport(Local::ReadExportSettings().singlePeer);
}); });
addButton(langFactory(lng_export_suggest_cancel), [=] { closeBox(); }); addButton(langFactory(lng_export_suggest_cancel), [=] { closeBox(); });
setCloseByOutsideClick(false); setCloseByOutsideClick(false);
@ -121,8 +121,11 @@ void PanelController::activatePanel() {
} }
void PanelController::createPanel() { void PanelController::createPanel() {
const auto singlePeer = _settings->onlySinglePeer();
_panel = base::make_unique_q<Ui::SeparatePanel>(); _panel = base::make_unique_q<Ui::SeparatePanel>();
_panel->setTitle(Lang::Viewer(lng_export_title)); _panel->setTitle(Lang::Viewer(singlePeer
? lng_export_header_chats
: lng_export_title));
_panel->setInnerSize(st::exportPanelSize); _panel->setInnerSize(st::exportPanelSize);
_panel->closeRequests( _panel->closeRequests(
) | rpl::start_with_next([=] { ) | rpl::start_with_next([=] {
@ -154,7 +157,6 @@ void PanelController::showSettings() {
settings->changes( settings->changes(
) | rpl::start_with_next([=](Settings &&settings) { ) | rpl::start_with_next([=](Settings &&settings) {
*_settings = std::move(settings); *_settings = std::move(settings);
_saveSettingsTimer.callOnce(kSaveSettingsTimeout);
}, settings->lifetime()); }, settings->lifetime());
_panel->showInner(std::move(settings)); _panel->showInner(std::move(settings));
@ -320,7 +322,14 @@ rpl::producer<> PanelController::stopRequests() const {
}); });
} }
void PanelController::fillParams(const PasswordCheckState &state) {
_settings->singlePeer = state.singlePeer;
}
void PanelController::updateState(State &&state) { void PanelController::updateState(State &&state) {
if (const auto start = base::get_if<PasswordCheckState>(&state)) {
fillParams(*start);
}
if (!_panel) { if (!_panel) {
createPanel(); createPanel();
} }

View file

@ -50,6 +50,7 @@ public:
~PanelController(); ~PanelController();
private: private:
void fillParams(const PasswordCheckState &state);
void stopExport(); void stopExport();
void createPanel(); void createPanel();
void updateState(State &&state); void updateState(State &&state);

View file

@ -20,6 +20,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/fade_wrap.h" #include "ui/wrap/fade_wrap.h"
#include "platform/platform_specific.h" #include "platform/platform_specific.h"
#include "core/file_utilities.h" #include "core/file_utilities.h"
#include "auth_session.h"
#include "styles/style_widgets.h" #include "styles/style_widgets.h"
#include "styles/style_export.h" #include "styles/style_export.h"
#include "styles/style_boxes.h" #include "styles/style_boxes.h"
@ -55,10 +56,25 @@ int SizeLimitByIndex(int index) {
return megabytes() * kMegabyte; return megabytes() * kMegabyte;
} }
PeerId ReadPeerId(const MTPInputPeer &data) {
return data.match([](const MTPDinputPeerUser &data) {
return peerFromUser(data.vuser_id.v);
}, [](const MTPDinputPeerChat &data) {
return peerFromChat(data.vchat_id.v);
}, [](const MTPDinputPeerChannel &data) {
return peerFromChannel(data.vchannel_id.v);
}, [](const MTPDinputPeerSelf &data) {
return Auth().userPeerId();
}, [](const MTPDinputPeerEmpty &data) {
return PeerId(0);
});
}
} // namespace } // namespace
SettingsWidget::SettingsWidget(QWidget *parent, Settings data) SettingsWidget::SettingsWidget(QWidget *parent, Settings data)
: RpWidget(parent) : RpWidget(parent)
, _singlePeerId(ReadPeerId(data.singlePeer))
, _internal_data(std::move(data)) { , _internal_data(std::move(data)) {
setupContent(); setupContent();
} }
@ -95,6 +111,17 @@ void SettingsWidget::setupContent() {
} }
void SettingsWidget::setupOptions(not_null<Ui::VerticalLayout*> container) { void SettingsWidget::setupOptions(not_null<Ui::VerticalLayout*> container) {
if (!_singlePeerId) {
setupFullExportOptions(container);
}
setupMediaOptions(container);
if (!_singlePeerId) {
setupOtherOptions(container);
}
}
void SettingsWidget::setupFullExportOptions(
not_null<Ui::VerticalLayout*> container) {
addOptionWithAbout( addOptionWithAbout(
container, container,
lng_export_option_info, lng_export_option_info,
@ -127,38 +154,21 @@ void SettingsWidget::setupOptions(not_null<Ui::VerticalLayout*> container) {
container, container,
lng_export_option_public_channels, lng_export_option_public_channels,
Type::PublicChannels); Type::PublicChannels);
setupMediaOptions(container);
addHeader(container, lng_export_header_other);
addOptionWithAbout(
container,
lng_export_option_sessions,
Type::Sessions,
lng_export_option_sessions_about);
addOptionWithAbout(
container,
lng_export_option_other,
Type::OtherData,
lng_export_option_other_about);
} }
void SettingsWidget::setupMediaOptions( void SettingsWidget::setupMediaOptions(
not_null<Ui::VerticalLayout*> container) { not_null<Ui::VerticalLayout*> container) {
if (_singlePeerId != 0) {
addMediaOptions(container);
return;
}
const auto mediaWrap = container->add( const auto mediaWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>( object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container, container,
object_ptr<Ui::VerticalLayout>(container))); object_ptr<Ui::VerticalLayout>(container)));
const auto media = mediaWrap->entity(); const auto media = mediaWrap->entity();
addHeader(media, lng_export_header_media); addHeader(media, lng_export_header_media);
addMediaOption(media, lng_export_option_photos, MediaType::Photo); addMediaOptions(media);
addMediaOption(media, lng_export_option_video_files, MediaType::Video);
addMediaOption(media, lng_export_option_voice_messages, MediaType::VoiceMessage);
addMediaOption(media, lng_export_option_video_messages, MediaType::VideoMessage);
addMediaOption(media, lng_export_option_stickers, MediaType::Sticker);
addMediaOption(media, lng_export_option_gifs, MediaType::GIF);
addMediaOption(media, lng_export_option_files, MediaType::File);
addSizeSlider(media);
value() | rpl::map([](const Settings &data) { value() | rpl::map([](const Settings &data) {
return data.types; return data.types;
@ -178,8 +188,27 @@ void SettingsWidget::setupMediaOptions(
}, mediaWrap->lifetime()); }, mediaWrap->lifetime());
} }
void SettingsWidget::setupOtherOptions(
not_null<Ui::VerticalLayout*> container) {
addHeader(container, lng_export_header_other);
addOptionWithAbout(
container,
lng_export_option_sessions,
Type::Sessions,
lng_export_option_sessions_about);
addOptionWithAbout(
container,
lng_export_option_other,
Type::OtherData,
lng_export_option_other_about);
}
void SettingsWidget::setupPathAndFormat( void SettingsWidget::setupPathAndFormat(
not_null<Ui::VerticalLayout*> container) { not_null<Ui::VerticalLayout*> container) {
if (_singlePeerId != 0) {
addLocationLabel(container);
return;
}
const auto formatGroup = std::make_shared<Ui::RadioenumGroup<Format>>( const auto formatGroup = std::make_shared<Ui::RadioenumGroup<Format>>(
readData().format); readData().format);
formatGroup->setChangedCallback([=](Format format) { formatGroup->setChangedCallback([=](Format format) {
@ -205,6 +234,7 @@ void SettingsWidget::setupPathAndFormat(
void SettingsWidget::addLocationLabel( void SettingsWidget::addLocationLabel(
not_null<Ui::VerticalLayout*> container) { not_null<Ui::VerticalLayout*> container) {
#ifndef OS_MAC_STORE
auto pathLabel = value() | rpl::map([](const Settings &data) { auto pathLabel = value() | rpl::map([](const Settings &data) {
return data.path; return data.path;
}) | rpl::distinct_until_changed( }) | rpl::distinct_until_changed(
@ -241,6 +271,7 @@ void SettingsWidget::addLocationLabel(
chooseFolder(); chooseFolder();
return false; return false;
}); });
#endif // OS_MAC_STORE
} }
not_null<Ui::RpWidget*> SettingsWidget::setupButtons( not_null<Ui::RpWidget*> SettingsWidget::setupButtons(
@ -382,6 +413,30 @@ void SettingsWidget::addChatOption(
} }
} }
void SettingsWidget::addMediaOptions(
not_null<Ui::VerticalLayout*> container) {
addMediaOption(container, lng_export_option_photos, MediaType::Photo);
addMediaOption(
container,
lng_export_option_video_files,
MediaType::Video);
addMediaOption(
container,
lng_export_option_voice_messages,
MediaType::VoiceMessage);
addMediaOption(
container,
lng_export_option_video_messages,
MediaType::VideoMessage);
addMediaOption(
container,
lng_export_option_stickers,
MediaType::Sticker);
addMediaOption(container, lng_export_option_gifs, MediaType::GIF);
addMediaOption(container, lng_export_option_files, MediaType::File);
addSizeSlider(container);
}
void SettingsWidget::addMediaOption( void SettingsWidget::addMediaOption(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
LangKey key, LangKey key,

View file

@ -42,7 +42,9 @@ private:
not_null<Ui::ScrollArea*> scroll, not_null<Ui::ScrollArea*> scroll,
not_null<Ui::RpWidget*> wrap); not_null<Ui::RpWidget*> wrap);
void setupOptions(not_null<Ui::VerticalLayout*> container); void setupOptions(not_null<Ui::VerticalLayout*> container);
void setupFullExportOptions(not_null<Ui::VerticalLayout*> container);
void setupMediaOptions(not_null<Ui::VerticalLayout*> container); void setupMediaOptions(not_null<Ui::VerticalLayout*> container);
void setupOtherOptions(not_null<Ui::VerticalLayout*> container);
void setupPathAndFormat(not_null<Ui::VerticalLayout*> container); void setupPathAndFormat(not_null<Ui::VerticalLayout*> container);
void addHeader( void addHeader(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
@ -60,6 +62,7 @@ private:
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
LangKey key, LangKey key,
Types types); Types types);
void addMediaOptions(not_null<Ui::VerticalLayout*> container);
void addMediaOption( void addMediaOption(
not_null<Ui::VerticalLayout*> container, not_null<Ui::VerticalLayout*> container,
LangKey key, LangKey key,
@ -76,6 +79,8 @@ private:
template <typename Callback> template <typename Callback>
void changeData(Callback &&callback); void changeData(Callback &&callback);
PeerId _singlePeerId = 0;
// Use through readData / changeData wrappers. // Use through readData / changeData wrappers.
Settings _internal_data; Settings _internal_data;

View file

@ -43,6 +43,12 @@ constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000);
constexpr auto kDefaultStickerInstallDate = TimeId(1); constexpr auto kDefaultStickerInstallDate = TimeId(1);
constexpr auto kProxyTypeShift = 1024; constexpr auto kProxyTypeShift = 1024;
constexpr auto kSinglePeerTypeUser = qint32(1);
constexpr auto kSinglePeerTypeChat = qint32(2);
constexpr auto kSinglePeerTypeChannel = qint32(3);
constexpr auto kSinglePeerTypeSelf = qint32(4);
constexpr auto kSinglePeerTypeEmpty = qint32(0);
using FileKey = quint64; using FileKey = quint64;
constexpr char tdfMagic[] = { 'T', 'D', 'F', '$' }; constexpr char tdfMagic[] = { 'T', 'D', 'F', '$' };
@ -4816,7 +4822,8 @@ void WriteExportSettings(const Export::Settings &settings) {
&& settings.media.sizeLimit == check.media.sizeLimit && settings.media.sizeLimit == check.media.sizeLimit
&& settings.path == check.path && settings.path == check.path
&& settings.format == check.format && settings.format == check.format
&& settings.availableAt == check.availableAt) { && settings.availableAt == check.availableAt
&& !settings.onlySinglePeer()) {
if (_exportSettingsKey) { if (_exportSettingsKey) {
clearKey(_exportSettingsKey); clearKey(_exportSettingsKey);
_exportSettingsKey = 0; _exportSettingsKey = 0;
@ -4830,7 +4837,8 @@ void WriteExportSettings(const Export::Settings &settings) {
_writeMap(WriteMapWhen::Fast); _writeMap(WriteMapWhen::Fast);
} }
quint32 size = sizeof(quint32) * 6 quint32 size = sizeof(quint32) * 6
+ Serialize::stringSize(settings.path); + Serialize::stringSize(settings.path)
+ sizeof(qint32) * 2 + sizeof(quint64);
EncryptedDescriptor data(size); EncryptedDescriptor data(size);
data.stream data.stream
<< quint32(settings.types) << quint32(settings.types)
@ -4840,6 +4848,23 @@ void WriteExportSettings(const Export::Settings &settings) {
<< quint32(settings.format) << quint32(settings.format)
<< settings.path << settings.path
<< quint32(settings.availableAt); << quint32(settings.availableAt);
settings.singlePeer.match([&](const MTPDinputPeerUser &user) {
data.stream
<< kSinglePeerTypeUser
<< qint32(user.vuser_id.v)
<< quint64(user.vaccess_hash.v);
}, [&](const MTPDinputPeerChat &chat) {
data.stream << kSinglePeerTypeChat << qint32(chat.vchat_id.v);
}, [&](const MTPDinputPeerChannel &channel) {
data.stream
<< kSinglePeerTypeChannel
<< qint32(channel.vchannel_id.v)
<< quint64(channel.vaccess_hash.v);
}, [&](const MTPDinputPeerSelf &) {
data.stream << kSinglePeerTypeSelf;
}, [&](const MTPDinputPeerEmpty &) {
data.stream << kSinglePeerTypeEmpty;
});
FileWriteDescriptor file(_exportSettingsKey); FileWriteDescriptor file(_exportSettingsKey);
file.writeEncrypted(data); file.writeEncrypted(data);
@ -4859,6 +4884,8 @@ Export::Settings ReadExportSettings() {
quint32 mediaTypes = 0, mediaSizeLimit = 0; quint32 mediaTypes = 0, mediaSizeLimit = 0;
quint32 format = 0, availableAt = 0; quint32 format = 0, availableAt = 0;
QString path; QString path;
qint32 singlePeerType = 0, singlePeerBareId = 0;
quint64 singlePeerAccessHash = 0;
file.stream file.stream
>> types >> types
>> fullChats >> fullChats
@ -4867,6 +4894,19 @@ Export::Settings ReadExportSettings() {
>> format >> format
>> path >> path
>> availableAt; >> availableAt;
if (!file.stream.atEnd()) {
file.stream >> singlePeerType;
switch (singlePeerType) {
case kSinglePeerTypeUser:
case kSinglePeerTypeChannel: {
file.stream >> singlePeerBareId >> singlePeerAccessHash;
} break;
case kSinglePeerTypeChat: file.stream >> singlePeerBareId; break;
case kSinglePeerTypeSelf:
case kSinglePeerTypeEmpty: break;
default: return Export::Settings();
}
}
auto result = Export::Settings(); auto result = Export::Settings();
result.types = Export::Settings::Types::from_raw(types); result.types = Export::Settings::Types::from_raw(types);
result.fullChats = Export::Settings::Types::from_raw(fullChats); result.fullChats = Export::Settings::Types::from_raw(fullChats);
@ -4875,6 +4915,25 @@ Export::Settings ReadExportSettings() {
result.format = Export::Output::Format(format); result.format = Export::Output::Format(format);
result.path = path; result.path = path;
result.availableAt = availableAt; result.availableAt = availableAt;
result.singlePeer = [&] {
switch (singlePeerType) {
case kSinglePeerTypeUser:
return MTP_inputPeerUser(
MTP_int(singlePeerBareId),
MTP_long(singlePeerAccessHash));
case kSinglePeerTypeChat:
return MTP_inputPeerChat(MTP_int(singlePeerBareId));
case kSinglePeerTypeChannel:
return MTP_inputPeerChannel(
MTP_int(singlePeerBareId),
MTP_long(singlePeerAccessHash));
case kSinglePeerTypeSelf:
return MTP_inputPeerSelf();
case kSinglePeerTypeEmpty:
return MTP_inputPeerEmpty();
}
Unexpected("Type in export data single peer.");
}();
return (file.stream.status() == QDataStream::Ok && result.validate()) return (file.stream.status() == QDataStream::Ok && result.validate())
? result ? result
: Export::Settings(); : Export::Settings();

View file

@ -342,6 +342,9 @@ void Filler::addUserActions(not_null<UserData*> user) {
lang(lng_profile_invite_to_group), lang(lng_profile_invite_to_group),
[user] { AddBotToGroupBoxController::Start(user); }); [user] { AddBotToGroupBoxController::Start(user); });
} }
_addAction(
lang(lng_profile_export_chat),
[=] { PeerMenuExportChat(user); });
} }
_addAction( _addAction(
lang(lng_profile_delete_conversation), lang(lng_profile_delete_conversation),
@ -364,6 +367,9 @@ void Filler::addChatActions(not_null<ChatData*> chat) {
lang(lng_profile_add_participant), lang(lng_profile_add_participant),
[chat] { AddChatMembers(chat); }); [chat] { AddChatMembers(chat); });
} }
_addAction(
lang(lng_profile_export_chat),
[=] { PeerMenuExportChat(chat); });
} }
_addAction( _addAction(
lang(lng_profile_clear_and_exit), lang(lng_profile_clear_and_exit),
@ -398,6 +404,11 @@ void Filler::addChannelActions(not_null<ChannelData*> channel) {
lang(lng_channel_add_members), lang(lng_channel_add_members),
[channel] { PeerMenuAddChannelMembers(channel); }); [channel] { PeerMenuAddChannelMembers(channel); });
} }
_addAction(
lang(isGroup
? lng_profile_export_chat
: lng_profile_export_channel),
[=] { PeerMenuExportChat(channel); });
} }
if (channel->amIn()) { if (channel->amIn()) {
if (isGroup && !channel->isPublic()) { if (isGroup && !channel->isPublic()) {
@ -542,6 +553,10 @@ void FeedFiller::addUngroup() {
} // namespace } // namespace
void PeerMenuExportChat(not_null<PeerData*> peer) {
Auth().data().startExport(peer);
}
void PeerMenuDeleteContact(not_null<UserData*> user) { void PeerMenuDeleteContact(not_null<UserData*> user) {
auto text = lng_sure_delete_contact( auto text = lng_sure_delete_contact(
lt_contact, lt_contact,

View file

@ -44,6 +44,7 @@ void PeerMenuAddMuteAction(
not_null<PeerData*> peer, not_null<PeerData*> peer,
const PeerMenuCallback &addAction); const PeerMenuCallback &addAction);
void PeerMenuExportChat(not_null<PeerData*> peer);
void PeerMenuDeleteContact(not_null<UserData*> user); void PeerMenuDeleteContact(not_null<UserData*> user);
void PeerMenuShareContactBox(not_null<UserData*> user); void PeerMenuShareContactBox(not_null<UserData*> user);
void PeerMenuAddContact(not_null<UserData*> user); void PeerMenuAddContact(not_null<UserData*> user);

@ -1 +1 @@
Subproject commit 9bc641f2d4ab140a84aea64c7f2d4669f7633246 Subproject commit 527ad273b683d52c5adf5b45b73c6466aa0d0cf0