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_block_user" = "Block 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#other" = "{count} Photos";
"lng_media_selected_video#one" = "{count} Video";

View file

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

View file

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

View file

@ -1386,19 +1386,36 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
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) {
auto result = DialogsInfo();
data.match([&](const auto &data) { //MTPDmessages_chats &data) {
result.left.reserve(data.vchats.v.size());
for (const auto &single : data.vchats.v) {
const auto chat = 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);
auto info = DialogInfoFromChat(ParseChat(single));
info.isLeftChannel = true;
result.left.push_back(std::move(info));
}
@ -1406,6 +1423,65 @@ DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data) {
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) {
auto &chats = info.chats;
auto &left = info.left;
@ -1414,7 +1490,9 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
auto index = 0;
for (auto &dialog : chats) {
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 Type = Settings::Type;
@ -1436,6 +1514,8 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
ranges::reverse(dialog.splits);
}
for (auto &dialog : left) {
Assert(!settings.onlySinglePeer());
const auto number = Data::NumberToString(++index, digits, '0');
dialog.relativePath = "chats/chat_" + number + '/';
dialog.onlyMyMessages = true;

View file

@ -546,6 +546,12 @@ struct DialogsInfo {
DialogInfo::Type DialogTypeFromChat(const Chat &chat);
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);
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(),
MTP_int(0),
MTP_bytes(QByteArray())));
} else if (result.type() == qstr("LOCATION_INVALID")) {
} else if (result.type() == qstr("LOCATION_INVALID")
|| result.type() == qstr("VERSION_INVALID")) {
filePartUnavailable();
} else {
error(std::move(result));
@ -413,7 +414,9 @@ void ApiWrap::startExport(
_startProcess->steps.push_back(Step::DialogsCount);
}
if (_settings->types & Settings::Type::GroupsChannelsMask) {
_startProcess->steps.push_back(Step::LeftChannelsCount);
if (!_settings->onlySinglePeer()) {
_startProcess->steps.push_back(Step::LeftChannelsCount);
}
}
startMainSession([=] {
sendNextStartRequest();
@ -489,6 +492,15 @@ void ApiWrap::requestSplitRanges() {
void ApiWrap::requestDialogsCount() {
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 offsetId = 0;
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() {
Expects(_dialogsProcess != nullptr);
if (_settings->onlySinglePeer()) {
requestSinglePeerDialog();
return;
}
const auto splitIndex = _dialogsProcess->splitIndexPlusOne - 1;
const auto hash = 0;
splitRequest(splitIndex, MTPmessages_GetDialogs(

View file

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

View file

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

View file

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

View file

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

View file

@ -19,8 +19,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export {
namespace Output {
QString NormalizePath(const QString &source) {
QDir folder(source);
QString NormalizePath(const Settings &settings) {
QDir folder(settings.path);
const auto path = folder.absolutePath();
auto result = path.endsWith('/') ? path : (path + '/');
if (!folder.exists()) {
@ -32,7 +32,9 @@ QString NormalizePath(const QString &source) {
return result;
}
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.month(), 2, 10, QChar('0')
).arg(date.year());

View file

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

View file

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

View file

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

View file

@ -45,7 +45,7 @@ void SuggestBox::prepare() {
addButton(langFactory(lng_box_ok), [=] {
closeBox();
Auth().data().startExport();
Auth().data().startExport(Local::ReadExportSettings().singlePeer);
});
addButton(langFactory(lng_export_suggest_cancel), [=] { closeBox(); });
setCloseByOutsideClick(false);
@ -121,8 +121,11 @@ void PanelController::activatePanel() {
}
void PanelController::createPanel() {
const auto singlePeer = _settings->onlySinglePeer();
_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->closeRequests(
) | rpl::start_with_next([=] {
@ -154,7 +157,6 @@ void PanelController::showSettings() {
settings->changes(
) | rpl::start_with_next([=](Settings &&settings) {
*_settings = std::move(settings);
_saveSettingsTimer.callOnce(kSaveSettingsTimeout);
}, settings->lifetime());
_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) {
if (const auto start = base::get_if<PasswordCheckState>(&state)) {
fillParams(*start);
}
if (!_panel) {
createPanel();
}

View file

@ -50,6 +50,7 @@ public:
~PanelController();
private:
void fillParams(const PasswordCheckState &state);
void stopExport();
void createPanel();
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 "platform/platform_specific.h"
#include "core/file_utilities.h"
#include "auth_session.h"
#include "styles/style_widgets.h"
#include "styles/style_export.h"
#include "styles/style_boxes.h"
@ -55,10 +56,25 @@ int SizeLimitByIndex(int index) {
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
SettingsWidget::SettingsWidget(QWidget *parent, Settings data)
: RpWidget(parent)
, _singlePeerId(ReadPeerId(data.singlePeer))
, _internal_data(std::move(data)) {
setupContent();
}
@ -95,6 +111,17 @@ void SettingsWidget::setupContent() {
}
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(
container,
lng_export_option_info,
@ -127,38 +154,21 @@ void SettingsWidget::setupOptions(not_null<Ui::VerticalLayout*> container) {
container,
lng_export_option_public_channels,
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(
not_null<Ui::VerticalLayout*> container) {
if (_singlePeerId != 0) {
addMediaOptions(container);
return;
}
const auto mediaWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
const auto media = mediaWrap->entity();
addHeader(media, lng_export_header_media);
addMediaOption(media, lng_export_option_photos, MediaType::Photo);
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);
addMediaOptions(media);
value() | rpl::map([](const Settings &data) {
return data.types;
@ -178,8 +188,27 @@ void SettingsWidget::setupMediaOptions(
}, 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(
not_null<Ui::VerticalLayout*> container) {
if (_singlePeerId != 0) {
addLocationLabel(container);
return;
}
const auto formatGroup = std::make_shared<Ui::RadioenumGroup<Format>>(
readData().format);
formatGroup->setChangedCallback([=](Format format) {
@ -205,6 +234,7 @@ void SettingsWidget::setupPathAndFormat(
void SettingsWidget::addLocationLabel(
not_null<Ui::VerticalLayout*> container) {
#ifndef OS_MAC_STORE
auto pathLabel = value() | rpl::map([](const Settings &data) {
return data.path;
}) | rpl::distinct_until_changed(
@ -241,6 +271,7 @@ void SettingsWidget::addLocationLabel(
chooseFolder();
return false;
});
#endif // OS_MAC_STORE
}
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(
not_null<Ui::VerticalLayout*> container,
LangKey key,

View file

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

View file

@ -43,6 +43,12 @@ constexpr auto kFileLoaderQueueStopTimeout = TimeMs(5000);
constexpr auto kDefaultStickerInstallDate = TimeId(1);
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;
constexpr char tdfMagic[] = { 'T', 'D', 'F', '$' };
@ -4816,7 +4822,8 @@ void WriteExportSettings(const Export::Settings &settings) {
&& settings.media.sizeLimit == check.media.sizeLimit
&& settings.path == check.path
&& settings.format == check.format
&& settings.availableAt == check.availableAt) {
&& settings.availableAt == check.availableAt
&& !settings.onlySinglePeer()) {
if (_exportSettingsKey) {
clearKey(_exportSettingsKey);
_exportSettingsKey = 0;
@ -4830,7 +4837,8 @@ void WriteExportSettings(const Export::Settings &settings) {
_writeMap(WriteMapWhen::Fast);
}
quint32 size = sizeof(quint32) * 6
+ Serialize::stringSize(settings.path);
+ Serialize::stringSize(settings.path)
+ sizeof(qint32) * 2 + sizeof(quint64);
EncryptedDescriptor data(size);
data.stream
<< quint32(settings.types)
@ -4840,6 +4848,23 @@ void WriteExportSettings(const Export::Settings &settings) {
<< quint32(settings.format)
<< settings.path
<< 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);
file.writeEncrypted(data);
@ -4859,6 +4884,8 @@ Export::Settings ReadExportSettings() {
quint32 mediaTypes = 0, mediaSizeLimit = 0;
quint32 format = 0, availableAt = 0;
QString path;
qint32 singlePeerType = 0, singlePeerBareId = 0;
quint64 singlePeerAccessHash = 0;
file.stream
>> types
>> fullChats
@ -4867,6 +4894,19 @@ Export::Settings ReadExportSettings() {
>> format
>> path
>> 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();
result.types = Export::Settings::Types::from_raw(types);
result.fullChats = Export::Settings::Types::from_raw(fullChats);
@ -4875,6 +4915,25 @@ Export::Settings ReadExportSettings() {
result.format = Export::Output::Format(format);
result.path = path;
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())
? result
: Export::Settings();

View file

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

View file

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

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