426 lines
12 KiB
C++
426 lines
12 KiB
C++
/*
|
|
This file is part of Telegram Desktop,
|
|
the official desktop application for the Telegram messaging service.
|
|
|
|
For license and copyright information please follow this link:
|
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|
*/
|
|
#include "export/view/export_view_panel_controller.h"
|
|
|
|
#include "export/view/export_view_settings.h"
|
|
#include "export/view/export_view_progress.h"
|
|
#include "export/export_manager.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "ui/widgets/separate_panel.h"
|
|
#include "ui/wrap/padding_wrap.h"
|
|
#include "mtproto/mtproto_config.h"
|
|
#include "ui/boxes/confirm_box.h"
|
|
#include "lang/lang_keys.h"
|
|
#include "storage/storage_account.h"
|
|
#include "core/application.h"
|
|
#include "core/file_utilities.h"
|
|
#include "main/main_session.h"
|
|
#include "data/data_session.h"
|
|
#include "base/platform/base_platform_info.h"
|
|
#include "base/unixtime.h"
|
|
#include "base/qt/qt_common_adapters.h"
|
|
#include "styles/style_export.h"
|
|
#include "styles/style_layers.h"
|
|
|
|
namespace Export {
|
|
namespace View {
|
|
namespace {
|
|
|
|
constexpr auto kSaveSettingsTimeout = crl::time(1000);
|
|
|
|
class SuggestBox : public Ui::BoxContent {
|
|
public:
|
|
SuggestBox(QWidget*, not_null<Main::Session*> session);
|
|
|
|
protected:
|
|
void prepare() override;
|
|
|
|
private:
|
|
const not_null<Main::Session*> _session;
|
|
|
|
};
|
|
|
|
SuggestBox::SuggestBox(QWidget*, not_null<Main::Session*> session)
|
|
: _session(session) {
|
|
}
|
|
|
|
void SuggestBox::prepare() {
|
|
setTitle(tr::lng_export_suggest_title());
|
|
|
|
addButton(tr::lng_box_ok(), [=] {
|
|
const auto session = _session;
|
|
closeBox();
|
|
Core::App().exportManager().start(
|
|
session,
|
|
session->local().readExportSettings().singlePeer);
|
|
});
|
|
addButton(tr::lng_export_suggest_cancel(), [=] { closeBox(); });
|
|
setCloseByOutsideClick(false);
|
|
|
|
const auto content = Ui::CreateChild<Ui::FlatLabel>(
|
|
this,
|
|
tr::lng_export_suggest_text(tr::now),
|
|
st::boxLabel);
|
|
widthValue(
|
|
) | rpl::start_with_next([=](int width) {
|
|
const auto contentWidth = width
|
|
- st::boxPadding.left()
|
|
- st::boxPadding.right();
|
|
content->resizeToWidth(contentWidth);
|
|
content->moveToLeft(st::boxPadding.left(), 0);
|
|
}, content->lifetime());
|
|
content->heightValue(
|
|
) | rpl::start_with_next([=](int height) {
|
|
setDimensions(st::boxWidth, height + st::boxPadding.bottom());
|
|
}, content->lifetime());
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Environment PrepareEnvironment(not_null<Main::Session*> session) {
|
|
auto result = Environment();
|
|
result.internalLinksDomain = session->serverConfig().internalLinksDomain;
|
|
result.aboutTelegram = tr::lng_export_about_telegram(tr::now).toUtf8();
|
|
result.aboutContacts = tr::lng_export_about_contacts(tr::now).toUtf8();
|
|
result.aboutFrequent = tr::lng_export_about_frequent(tr::now).toUtf8();
|
|
result.aboutSessions = tr::lng_export_about_sessions(tr::now).toUtf8();
|
|
result.aboutWebSessions = tr::lng_export_about_web_sessions(tr::now).toUtf8();
|
|
result.aboutChats = tr::lng_export_about_chats(tr::now).toUtf8();
|
|
result.aboutLeftChats = tr::lng_export_about_left_chats(tr::now).toUtf8();
|
|
return result;
|
|
}
|
|
|
|
QPointer<Ui::BoxContent> SuggestStart(not_null<Main::Session*> session) {
|
|
ClearSuggestStart(session);
|
|
return Ui::show(
|
|
Box<SuggestBox>(session),
|
|
Ui::LayerOption::KeepOther).data();
|
|
}
|
|
|
|
void ClearSuggestStart(not_null<Main::Session*> session) {
|
|
session->data().clearExportSuggestion();
|
|
|
|
auto settings = session->local().readExportSettings();
|
|
if (settings.availableAt) {
|
|
settings.availableAt = 0;
|
|
session->local().writeExportSettings(settings);
|
|
}
|
|
}
|
|
|
|
bool IsDefaultPath(not_null<Main::Session*> session, const QString &path) {
|
|
const auto check = [](const QString &value) {
|
|
const auto result = value.endsWith('/')
|
|
? value.mid(0, value.size() - 1)
|
|
: value;
|
|
return Platform::IsWindows() ? result.toLower() : result;
|
|
};
|
|
return (check(path) == check(File::DefaultDownloadPath(session)));
|
|
}
|
|
|
|
void ResolveSettings(not_null<Main::Session*> session, Settings &settings) {
|
|
if (settings.path.isEmpty()) {
|
|
settings.path = File::DefaultDownloadPath(session);
|
|
settings.forceSubPath = true;
|
|
} else {
|
|
settings.forceSubPath = IsDefaultPath(session, settings.path);
|
|
}
|
|
if (!settings.onlySinglePeer()) {
|
|
settings.singlePeerFrom = settings.singlePeerTill = 0;
|
|
}
|
|
}
|
|
|
|
PanelController::PanelController(
|
|
not_null<Main::Session*> session,
|
|
not_null<Controller*> process)
|
|
: _session(session)
|
|
, _process(process)
|
|
, _settings(
|
|
std::make_unique<Settings>(_session->local().readExportSettings()))
|
|
, _saveSettingsTimer([=] { saveSettings(); }) {
|
|
ResolveSettings(session, *_settings);
|
|
|
|
_process->state(
|
|
) | rpl::start_with_next([=](State &&state) {
|
|
updateState(std::move(state));
|
|
}, _lifetime);
|
|
}
|
|
|
|
PanelController::~PanelController() {
|
|
if (_saveSettingsTimer.isActive()) {
|
|
saveSettings();
|
|
}
|
|
if (_panel) {
|
|
_panel->destroyLayer();
|
|
}
|
|
}
|
|
|
|
void PanelController::activatePanel() {
|
|
if (_panel) {
|
|
_panel->showAndActivate();
|
|
}
|
|
}
|
|
|
|
void PanelController::createPanel() {
|
|
const auto singlePeer = _settings->onlySinglePeer();
|
|
_panel = base::make_unique_q<Ui::SeparatePanel>();
|
|
_panel->setTitle((singlePeer
|
|
? tr::lng_export_header_chats
|
|
: tr::lng_export_title)());
|
|
_panel->setInnerSize(st::exportPanelSize);
|
|
_panel->closeRequests(
|
|
) | rpl::start_with_next([=] {
|
|
LOG(("Export Info: Panel Hide By Close."));
|
|
_panel->hideGetDuration();
|
|
}, _panel->lifetime());
|
|
_panelCloseEvents.fire(_panel->closeEvents());
|
|
|
|
showSettings();
|
|
}
|
|
|
|
void PanelController::showSettings() {
|
|
auto settings = base::make_unique_q<SettingsWidget>(
|
|
_panel,
|
|
_session,
|
|
*_settings);
|
|
settings->setShowBoxCallback([=](object_ptr<Ui::BoxContent> box) {
|
|
_panel->showBox(
|
|
std::move(box),
|
|
Ui::LayerOption::KeepOther,
|
|
anim::type::normal);
|
|
});
|
|
|
|
settings->startClicks(
|
|
) | rpl::start_with_next([=]() {
|
|
showProgress();
|
|
_process->startExport(*_settings, PrepareEnvironment(_session));
|
|
}, settings->lifetime());
|
|
|
|
settings->cancelClicks(
|
|
) | rpl::start_with_next([=] {
|
|
LOG(("Export Info: Panel Hide By Cancel."));
|
|
_panel->hideGetDuration();
|
|
}, settings->lifetime());
|
|
|
|
settings->changes(
|
|
) | rpl::start_with_next([=](Settings &&settings) {
|
|
*_settings = std::move(settings);
|
|
}, settings->lifetime());
|
|
|
|
_panel->showInner(std::move(settings));
|
|
}
|
|
|
|
void PanelController::showError(const ApiErrorState &error) {
|
|
LOG(("Export Info: API Error '%1'.").arg(error.data.type()));
|
|
|
|
if (error.data.type() == qstr("TAKEOUT_INVALID")) {
|
|
showError(tr::lng_export_invalid(tr::now));
|
|
} else if (error.data.type().startsWith(qstr("TAKEOUT_INIT_DELAY_"))) {
|
|
const auto seconds = std::max(base::StringViewMid(
|
|
error.data.type(),
|
|
qstr("TAKEOUT_INIT_DELAY_").size()).toInt(), 1);
|
|
const auto now = QDateTime::currentDateTime();
|
|
const auto when = now.addSecs(seconds);
|
|
const auto hours = seconds / 3600;
|
|
const auto hoursText = [&] {
|
|
if (hours <= 0) {
|
|
return tr::lng_export_delay_less_than_hour(tr::now);
|
|
}
|
|
return tr::lng_export_delay_hours(tr::now, lt_count, hours);
|
|
}();
|
|
showError(tr::lng_export_delay(
|
|
tr::now,
|
|
lt_hours,
|
|
hoursText,
|
|
lt_date,
|
|
langDateTimeFull(when)));
|
|
|
|
_settings->availableAt = base::unixtime::now() + seconds;
|
|
_saveSettingsTimer.callOnce(kSaveSettingsTimeout);
|
|
|
|
_session->data().suggestStartExport(_settings->availableAt);
|
|
} else {
|
|
showCriticalError("API Error happened :(\n"
|
|
+ QString::number(error.data.code()) + ": " + error.data.type()
|
|
+ "\n" + error.data.description());
|
|
}
|
|
}
|
|
|
|
void PanelController::showError(const OutputErrorState &error) {
|
|
showCriticalError("Disk Error happened :(\n"
|
|
"Could not write path:\n" + error.path);
|
|
}
|
|
|
|
void PanelController::showCriticalError(const QString &text) {
|
|
auto container = base::make_unique_q<Ui::PaddingWrap<Ui::FlatLabel>>(
|
|
_panel.get(),
|
|
object_ptr<Ui::FlatLabel>(
|
|
_panel.get(),
|
|
text,
|
|
st::exportErrorLabel),
|
|
style::margins(0, st::exportPanelSize.height() / 4, 0, 0));
|
|
container->widthValue(
|
|
) | rpl::start_with_next([label = container->entity()](int width) {
|
|
label->resize(width, label->height());
|
|
}, container->lifetime());
|
|
|
|
_panel->showInner(std::move(container));
|
|
_panel->setHideOnDeactivate(false);
|
|
}
|
|
|
|
void PanelController::showError(const QString &text) {
|
|
auto box = Box<Ui::InformBox>(text);
|
|
const auto weak = Ui::MakeWeak(box.data());
|
|
const auto hidden = _panel->isHidden();
|
|
_panel->showBox(
|
|
std::move(box),
|
|
Ui::LayerOption::CloseOther,
|
|
hidden ? anim::type::instant : anim::type::normal);
|
|
weak->setCloseByEscape(false);
|
|
weak->setCloseByOutsideClick(false);
|
|
weak->boxClosing(
|
|
) | rpl::start_with_next([=] {
|
|
LOG(("Export Info: Panel Hide By Error: %1.").arg(text));
|
|
_panel->hideGetDuration();
|
|
}, weak->lifetime());
|
|
if (hidden) {
|
|
_panel->showAndActivate();
|
|
}
|
|
_panel->setHideOnDeactivate(false);
|
|
}
|
|
|
|
void PanelController::showProgress() {
|
|
_settings->availableAt = 0;
|
|
ClearSuggestStart(_session);
|
|
|
|
_panel->setTitle(tr::lng_export_progress_title());
|
|
|
|
auto progress = base::make_unique_q<ProgressWidget>(
|
|
_panel.get(),
|
|
rpl::single(
|
|
ContentFromState(_settings.get(), ProcessingState())
|
|
) | rpl::then(progressState()));
|
|
|
|
progress->skipFileClicks(
|
|
) | rpl::start_with_next([=](uint64 randomId) {
|
|
_process->skipFile(randomId);
|
|
}, progress->lifetime());
|
|
|
|
progress->cancelClicks(
|
|
) | rpl::start_with_next([=] {
|
|
stopWithConfirmation();
|
|
}, progress->lifetime());
|
|
|
|
progress->doneClicks(
|
|
) | rpl::start_with_next([=] {
|
|
if (const auto finished = std::get_if<FinishedState>(&_state)) {
|
|
File::ShowInFolder(finished->path);
|
|
LOG(("Export Info: Panel Hide By Done: %1."
|
|
).arg(finished->path));
|
|
_panel->hideGetDuration();
|
|
}
|
|
}, progress->lifetime());
|
|
|
|
_panel->showInner(std::move(progress));
|
|
_panel->setHideOnDeactivate(true);
|
|
}
|
|
|
|
void PanelController::stopWithConfirmation(FnMut<void()> callback) {
|
|
if (!v::is<ProcessingState>(_state)) {
|
|
LOG(("Export Info: Stop Panel Without Confirmation."));
|
|
stopExport();
|
|
if (callback) {
|
|
callback();
|
|
}
|
|
return;
|
|
}
|
|
auto stop = [=, callback = std::move(callback)]() mutable {
|
|
if (auto saved = std::move(callback)) {
|
|
LOG(("Export Info: Stop Panel With Confirmation."));
|
|
stopExport();
|
|
saved();
|
|
} else {
|
|
_process->cancelExportFast();
|
|
}
|
|
};
|
|
const auto hidden = _panel->isHidden();
|
|
const auto old = _confirmStopBox;
|
|
auto box = Box<Ui::ConfirmBox>(
|
|
tr::lng_export_sure_stop(tr::now),
|
|
tr::lng_export_stop(tr::now),
|
|
st::attentionBoxButton,
|
|
std::move(stop));
|
|
_confirmStopBox = box.data();
|
|
_panel->showBox(
|
|
std::move(box),
|
|
Ui::LayerOption::CloseOther,
|
|
hidden ? anim::type::instant : anim::type::normal);
|
|
if (hidden) {
|
|
_panel->showAndActivate();
|
|
}
|
|
if (old) {
|
|
old->closeBox();
|
|
}
|
|
}
|
|
|
|
void PanelController::stopExport() {
|
|
_stopRequested = true;
|
|
_panel->showAndActivate();
|
|
LOG(("Export Info: Panel Hide By Stop"));
|
|
_panel->hideGetDuration();
|
|
}
|
|
|
|
rpl::producer<> PanelController::stopRequests() const {
|
|
return _panelCloseEvents.events(
|
|
) | rpl::flatten_latest(
|
|
) | rpl::filter([=] {
|
|
return !v::is<ProcessingState>(_state) || _stopRequested;
|
|
});
|
|
}
|
|
|
|
void PanelController::fillParams(const PasswordCheckState &state) {
|
|
_settings->singlePeer = state.singlePeer;
|
|
}
|
|
|
|
void PanelController::updateState(State &&state) {
|
|
if (const auto start = std::get_if<PasswordCheckState>(&state)) {
|
|
fillParams(*start);
|
|
}
|
|
if (!_panel) {
|
|
createPanel();
|
|
}
|
|
_state = std::move(state);
|
|
if (const auto apiError = std::get_if<ApiErrorState>(&_state)) {
|
|
showError(*apiError);
|
|
} else if (const auto error = std::get_if<OutputErrorState>(&_state)) {
|
|
showError(*error);
|
|
} else if (v::is<FinishedState>(_state)) {
|
|
_panel->setTitle(tr::lng_export_title());
|
|
_panel->setHideOnDeactivate(false);
|
|
} else if (v::is<CancelledState>(_state)) {
|
|
LOG(("Export Info: Stop Panel After Cancel."));
|
|
stopExport();
|
|
}
|
|
}
|
|
|
|
void PanelController::saveSettings() const {
|
|
const auto check = [](const QString &value) {
|
|
const auto result = value.endsWith('/')
|
|
? value.mid(0, value.size() - 1)
|
|
: value;
|
|
return Platform::IsWindows() ? result.toLower() : result;
|
|
};
|
|
auto settings = *_settings;
|
|
if (check(settings.path) == check(File::DefaultDownloadPath(_session))) {
|
|
settings.path = QString();
|
|
}
|
|
_session->local().writeExportSettings(settings);
|
|
}
|
|
|
|
} // namespace View
|
|
} // namespace Export
|