tdesktop/Telegram/SourceFiles/export/export_controller.cpp

687 lines
18 KiB
C++
Raw Normal View History

2018-06-02 14:29:21 +00:00
/*
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/export_controller.h"
#include "export/export_api_wrap.h"
2018-06-02 14:29:21 +00:00
#include "export/export_settings.h"
#include "export/data/export_data_types.h"
#include "export/output/export_output_abstract.h"
#include "export/output/export_output_result.h"
#include "export/output/export_output_stats.h"
#include "mtproto/mtp_instance.h"
2018-06-02 14:29:21 +00:00
namespace Export {
2018-07-23 13:11:56 +00:00
namespace {
2018-06-02 14:29:21 +00:00
2018-07-23 13:11:56 +00:00
const auto kNullStateCallback = [](ProcessingState&) {};
Settings NormalizeSettings(const Settings &settings) {
if (!settings.onlySinglePeer()) {
return base::duplicate(settings);
}
auto result = base::duplicate(settings);
result.types = result.fullChats = Settings::Type::AnyChatsMask;
return result;
}
} // namespace
2018-06-18 21:52:13 +00:00
class ControllerObject {
2018-06-02 14:29:21 +00:00
public:
ControllerObject(
crl::weak_on_queue<ControllerObject> weak,
QPointer<MTP::Instance> mtproto,
2018-07-23 13:11:56 +00:00
const MTPInputPeer &peer);
2018-06-02 14:29:21 +00:00
rpl::producer<State> state() const;
// Password step.
2018-06-18 21:52:13 +00:00
//void submitPassword(const QString &password);
//void requestPasswordRecover();
//rpl::producer<PasswordUpdate> passwordUpdate() const;
//void reloadPasswordState();
//void cancelUnconfirmedPassword();
2018-06-02 14:29:21 +00:00
// Processing step.
2018-06-24 02:06:11 +00:00
void startExport(
const Settings &settings,
const Environment &environment);
void skipFile(uint64 randomId);
2018-06-21 00:54:59 +00:00
void cancelExportFast();
2018-06-02 14:29:21 +00:00
private:
using Step = ProcessingState::Step;
using DownloadProgress = ApiWrap::DownloadProgress;
[[nodiscard]] bool stopped() const;
2018-06-02 14:29:21 +00:00
void setState(State &&state);
void ioError(const QString &path);
bool ioCatchError(Output::Result result);
2018-06-02 14:29:21 +00:00
void setFinishedState();
2018-06-18 21:52:13 +00:00
//void requestPasswordState();
//void passwordStateDone(const MTPaccount_Password &password);
2018-06-02 14:29:21 +00:00
void fillExportSteps();
2018-06-18 21:52:13 +00:00
void fillSubstepsInSteps(const ApiWrap::StartInfo &info);
void exportNext();
2018-06-18 21:52:13 +00:00
void initialize();
2018-06-24 02:06:11 +00:00
void initialized(const ApiWrap::StartInfo &info);
2018-06-18 21:52:13 +00:00
void collectDialogsList();
void exportPersonalInfo();
void exportUserpics();
void exportContacts();
void exportSessions();
2018-06-24 00:33:47 +00:00
void exportOtherData();
2018-06-12 18:09:21 +00:00
void exportDialogs();
2018-06-18 21:52:13 +00:00
void exportNextDialog();
template <typename Callback = const decltype(kNullStateCallback) &>
ProcessingState prepareState(
Step step,
Callback &&callback = kNullStateCallback) const;
ProcessingState stateInitializing() const;
ProcessingState stateDialogsList(int processed) const;
ProcessingState statePersonalInfo() const;
2018-06-19 18:31:30 +00:00
ProcessingState stateUserpics(const DownloadProgress &progress) const;
2018-06-18 21:52:13 +00:00
ProcessingState stateContacts() const;
ProcessingState stateSessions() const;
2018-06-24 00:33:47 +00:00
ProcessingState stateOtherData() const;
2018-06-19 18:31:30 +00:00
ProcessingState stateDialogs(const DownloadProgress &progress) const;
void fillMessagesState(
ProcessingState &result,
const Data::DialogsInfo &info,
int index,
2018-07-11 21:06:35 +00:00
const DownloadProgress &progress) const;
2018-06-18 21:52:13 +00:00
int substepsInStep(Step step) const;
ApiWrap _api;
2018-06-02 14:29:21 +00:00
Settings _settings;
2018-06-24 02:06:11 +00:00
Environment _environment;
Data::DialogsInfo _dialogsInfo;
2018-06-18 21:52:13 +00:00
int _dialogIndex = -1;
2018-06-02 14:29:21 +00:00
int _messagesWritten = 0;
int _messagesCount = 0;
int _userpicsWritten = 0;
int _userpicsCount = 0;
2018-06-02 14:29:21 +00:00
// rpl::variable<State> fails to compile in MSVC :(
State _state;
rpl::event_stream<State> _stateChanges;
Output::Stats _stats;
2018-06-19 18:31:30 +00:00
std::vector<int> _substepsInStep;
int _substepsTotal = 0;
mutable int _substepsPassed = 0;
mutable Step _lastProcessingStep = Step::Initializing;
2018-06-02 14:29:21 +00:00
std::unique_ptr<Output::AbstractWriter> _writer;
2018-06-18 21:52:13 +00:00
std::vector<Step> _steps;
int _stepIndex = -1;
rpl::lifetime _lifetime;
2018-06-02 14:29:21 +00:00
};
ControllerObject::ControllerObject(
crl::weak_on_queue<ControllerObject> weak,
QPointer<MTP::Instance> mtproto,
2018-07-23 13:11:56 +00:00
const MTPInputPeer &peer)
: _api(mtproto, weak.runner())
2018-06-02 14:29:21 +00:00
, _state(PasswordCheckState{}) {
_api.errors(
2021-03-12 12:48:00 +00:00
) | rpl::start_with_next([=](const MTP::Error &error) {
setState(ApiErrorState{ error });
}, _lifetime);
_api.ioErrors(
) | rpl::start_with_next([=](const Output::Result &result) {
ioCatchError(result);
}, _lifetime);
//requestPasswordState();
auto state = PasswordCheckState();
state.checked = false;
state.requesting = false;
2018-07-23 13:11:56 +00:00
state.singlePeer = peer;
setState(std::move(state));
2018-06-02 14:29:21 +00:00
}
rpl::producer<State> ControllerObject::state() const {
2018-06-02 14:29:21 +00:00
return rpl::single(
_state
) | rpl::then(
_stateChanges.events()
) | rpl::filter([](const State &state) {
const auto password = std::get_if<PasswordCheckState>(&state);
2018-06-02 14:29:21 +00:00
return !password || !password->requesting;
});
}
bool ControllerObject::stopped() const {
return v::is<CancelledState>(_state)
|| v::is<ApiErrorState>(_state)
|| v::is<OutputErrorState>(_state)
|| v::is<FinishedState>(_state);
}
void ControllerObject::setState(State &&state) {
if (stopped()) {
2018-06-21 00:54:59 +00:00
return;
}
2018-06-02 14:29:21 +00:00
_state = std::move(state);
_stateChanges.fire_copy(_state);
}
void ControllerObject::ioError(const QString &path) {
setState(OutputErrorState{ path });
}
bool ControllerObject::ioCatchError(Output::Result result) {
if (!result) {
ioError(result.path);
return true;
}
return false;
2018-06-02 14:29:21 +00:00
}
//void ControllerObject::submitPassword(const QString &password) {
2018-06-18 21:52:13 +00:00
//
//}
//
//void ControllerObject::requestPasswordRecover() {
2018-06-18 21:52:13 +00:00
//
//}
//
//rpl::producer<PasswordUpdate> ControllerObject::passwordUpdate() const {
// return nullptr;
2018-06-18 21:52:13 +00:00
//}
//
//void ControllerObject::reloadPasswordState() {
2018-06-18 21:52:13 +00:00
// //_mtp.request(base::take(_passwordRequestId)).cancel();
// requestPasswordState();
//}
//
//void ControllerObject::requestPasswordState() {
2018-06-18 21:52:13 +00:00
// if (_passwordRequestId) {
// return;
// }
// //_passwordRequestId = _mtp.request(MTPaccount_GetPassword(
// //)).done([=](const MTPaccount_Password &result) {
// // _passwordRequestId = 0;
// // passwordStateDone(result);
2021-03-12 12:48:00 +00:00
// //}).fail([=](const MTP::Error &error) {
2018-06-18 21:52:13 +00:00
// // apiError(error);
// //}).send();
//}
//
//void ControllerObject::passwordStateDone(const MTPaccount_Password &result) {
2018-06-18 21:52:13 +00:00
// auto state = PasswordCheckState();
// state.checked = false;
// state.requesting = false;
// state.hasPassword;
// state.hint;
// state.unconfirmedPattern;
// setState(std::move(state));
//}
//
//void ControllerObject::cancelUnconfirmedPassword() {
2018-06-18 21:52:13 +00:00
//
//}
2018-06-02 14:29:21 +00:00
void ControllerObject::startExport(
2018-06-24 02:06:11 +00:00
const Settings &settings,
const Environment &environment) {
if (!_settings.path.isEmpty()) {
return;
}
2018-07-23 13:11:56 +00:00
_settings = NormalizeSettings(settings);
2018-06-24 02:06:11 +00:00
_environment = environment;
2018-06-02 14:29:21 +00:00
2018-07-23 13:11:56 +00:00
_settings.path = Output::NormalizePath(_settings);
_writer = Output::CreateWriter(_settings.format);
fillExportSteps();
2018-06-18 21:52:13 +00:00
exportNext();
}
void ControllerObject::skipFile(uint64 randomId) {
if (stopped()) {
return;
}
_api.skipFile(randomId);
}
void ControllerObject::fillExportSteps() {
using Type = Settings::Type;
2018-06-18 21:52:13 +00:00
_steps.push_back(Step::Initializing);
if (_settings.types & Type::AnyChatsMask) {
_steps.push_back(Step::DialogsList);
}
if (_settings.types & Type::PersonalInfo) {
_steps.push_back(Step::PersonalInfo);
}
if (_settings.types & Type::Userpics) {
_steps.push_back(Step::Userpics);
}
if (_settings.types & Type::Contacts) {
_steps.push_back(Step::Contacts);
}
if (_settings.types & Type::Sessions) {
_steps.push_back(Step::Sessions);
}
2018-06-24 00:33:47 +00:00
if (_settings.types & Type::OtherData) {
_steps.push_back(Step::OtherData);
}
2018-06-18 21:52:13 +00:00
if (_settings.types & Type::AnyChatsMask) {
2018-06-12 18:09:21 +00:00
_steps.push_back(Step::Dialogs);
}
}
void ControllerObject::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
auto result = std::vector<int>();
2018-06-18 21:52:13 +00:00
const auto push = [&](Step step, int count) {
const auto index = static_cast<int>(step);
if (index >= result.size()) {
result.resize(index + 1, 0);
2018-06-18 21:52:13 +00:00
}
result[index] = count;
2018-06-18 21:52:13 +00:00
};
push(Step::Initializing, 1);
if (_settings.types & Settings::Type::AnyChatsMask) {
push(Step::DialogsList, 1);
}
if (_settings.types & Settings::Type::PersonalInfo) {
push(Step::PersonalInfo, 1);
}
if (_settings.types & Settings::Type::Userpics) {
2018-06-19 18:31:30 +00:00
push(Step::Userpics, 1);
2018-06-18 21:52:13 +00:00
}
if (_settings.types & Settings::Type::Contacts) {
push(Step::Contacts, 1);
}
if (_settings.types & Settings::Type::Sessions) {
push(Step::Sessions, 1);
}
2018-06-24 00:33:47 +00:00
if (_settings.types & Settings::Type::OtherData) {
push(Step::OtherData, 1);
}
2018-06-18 21:52:13 +00:00
if (_settings.types & Settings::Type::AnyChatsMask) {
push(Step::Dialogs, info.dialogsCount);
}
2018-06-19 18:31:30 +00:00
_substepsInStep = std::move(result);
_substepsTotal = ranges::accumulate(_substepsInStep, 0);
2018-06-18 21:52:13 +00:00
}
void ControllerObject::cancelExportFast() {
2018-06-21 00:54:59 +00:00
_api.cancelExportFast();
setState(CancelledState());
}
void ControllerObject::exportNext() {
2018-06-21 20:42:50 +00:00
if (++_stepIndex >= _steps.size()) {
if (ioCatchError(_writer->finish())) {
return;
}
2018-06-21 00:54:59 +00:00
_api.finishExport([=] {
setFinishedState();
});
return;
}
2018-06-21 00:54:59 +00:00
const auto step = _steps[_stepIndex];
switch (step) {
2018-06-18 21:52:13 +00:00
case Step::Initializing: return initialize();
case Step::DialogsList: return collectDialogsList();
case Step::PersonalInfo: return exportPersonalInfo();
case Step::Userpics: return exportUserpics();
case Step::Contacts: return exportContacts();
case Step::Sessions: return exportSessions();
2018-06-24 00:33:47 +00:00
case Step::OtherData: return exportOtherData();
2018-06-12 18:09:21 +00:00
case Step::Dialogs: return exportDialogs();
}
Unexpected("Step in ControllerObject::exportNext.");
}
void ControllerObject::initialize() {
2018-06-18 21:52:13 +00:00
setState(stateInitializing());
_api.startExport(_settings, &_stats, [=](ApiWrap::StartInfo info) {
2018-06-24 02:06:11 +00:00
initialized(info);
2018-06-18 21:52:13 +00:00
});
}
void ControllerObject::initialized(const ApiWrap::StartInfo &info) {
2018-06-24 02:06:11 +00:00
if (ioCatchError(_writer->start(_settings, _environment, &_stats))) {
return;
}
fillSubstepsInSteps(info);
exportNext();
}
void ControllerObject::collectDialogsList() {
2018-06-24 00:33:47 +00:00
setState(stateDialogsList(0));
_api.requestDialogsList([=](int count) {
if (count > 0) {
setState(stateDialogsList(count - 1));
}
return true;
}, [=](Data::DialogsInfo &&result) {
2018-06-18 21:52:13 +00:00
_dialogsInfo = std::move(result);
exportNext();
});
}
void ControllerObject::exportPersonalInfo() {
2018-06-24 00:33:47 +00:00
setState(statePersonalInfo());
_api.requestPersonalInfo([=](Data::PersonalInfo &&result) {
if (ioCatchError(_writer->writePersonal(result))) {
return;
}
exportNext();
});
}
2018-06-02 14:29:21 +00:00
void ControllerObject::exportUserpics() {
_api.requestUserpics([=](Data::UserpicsInfo &&start) {
if (ioCatchError(_writer->writeUserpicsStart(start))) {
return false;
}
_userpicsWritten = 0;
_userpicsCount = start.count;
return true;
}, [=](DownloadProgress progress) {
setState(stateUserpics(progress));
return true;
}, [=](Data::UserpicsSlice &&slice) {
if (ioCatchError(_writer->writeUserpicsSlice(slice))) {
return false;
}
_userpicsWritten += slice.list.size();
setState(stateUserpics(DownloadProgress()));
return true;
}, [=] {
if (ioCatchError(_writer->writeUserpicsEnd())) {
return;
}
exportNext();
});
}
void ControllerObject::exportContacts() {
2018-06-24 00:33:47 +00:00
setState(stateContacts());
_api.requestContacts([=](Data::ContactsList &&result) {
if (ioCatchError(_writer->writeContactsList(result))) {
return;
}
exportNext();
});
}
void ControllerObject::exportSessions() {
2018-06-24 00:33:47 +00:00
setState(stateSessions());
2018-06-11 18:57:56 +00:00
_api.requestSessions([=](Data::SessionsList &&result) {
if (ioCatchError(_writer->writeSessionsList(result))) {
return;
}
2018-06-11 18:57:56 +00:00
exportNext();
});
}
void ControllerObject::exportOtherData() {
2018-06-24 00:33:47 +00:00
setState(stateOtherData());
const auto relativePath = "lists/other_data.json";
_api.requestOtherData(relativePath, [=](Data::File &&result) {
if (ioCatchError(_writer->writeOtherData(result))) {
return;
}
exportNext();
});
}
void ControllerObject::exportDialogs() {
if (ioCatchError(_writer->writeDialogsStart(_dialogsInfo))) {
return;
}
2018-06-18 21:52:13 +00:00
exportNextDialog();
}
void ControllerObject::exportNextDialog() {
2018-06-18 21:52:13 +00:00
const auto index = ++_dialogIndex;
2018-07-11 21:06:35 +00:00
const auto info = _dialogsInfo.item(index);
if (info) {
_api.requestMessages(*info, [=](const Data::DialogInfo &info) {
if (ioCatchError(_writer->writeDialogStart(info))) {
return false;
}
_messagesWritten = 0;
_messagesCount = ranges::accumulate(
info.messagesCountPerSplit,
0);
setState(stateDialogs(DownloadProgress()));
return true;
}, [=](DownloadProgress progress) {
setState(stateDialogs(progress));
return true;
}, [=](Data::MessagesSlice &&result) {
if (ioCatchError(_writer->writeDialogSlice(result))) {
return false;
}
_messagesWritten += result.list.size();
setState(stateDialogs(DownloadProgress()));
return true;
2018-06-18 21:52:13 +00:00
}, [=] {
if (ioCatchError(_writer->writeDialogEnd())) {
return;
}
2018-06-18 21:52:13 +00:00
exportNextDialog();
});
return;
}
if (ioCatchError(_writer->writeDialogsEnd())) {
return;
}
2018-06-18 21:52:13 +00:00
exportNext();
2018-06-02 14:29:21 +00:00
}
2018-06-18 21:52:13 +00:00
template <typename Callback>
ProcessingState ControllerObject::prepareState(
2018-06-18 21:52:13 +00:00
Step step,
Callback &&callback) const {
2018-06-19 18:31:30 +00:00
if (step != _lastProcessingStep) {
_substepsPassed += substepsInStep(_lastProcessingStep);
_lastProcessingStep = step;
}
2018-06-18 21:52:13 +00:00
auto result = ProcessingState();
callback(result);
result.step = step;
2018-06-19 18:31:30 +00:00
result.substepsPassed = _substepsPassed;
result.substepsNow = substepsInStep(_lastProcessingStep);
result.substepsTotal = _substepsTotal;
2018-06-18 21:52:13 +00:00
return result;
}
ProcessingState ControllerObject::stateInitializing() const {
2018-06-18 21:52:13 +00:00
return ProcessingState();
}
ProcessingState ControllerObject::stateDialogsList(int processed) const {
2018-06-18 21:52:13 +00:00
const auto step = Step::DialogsList;
return prepareState(step, [&](ProcessingState &result) {
2018-07-11 21:06:35 +00:00
result.entityIndex = processed;
result.entityCount = std::max(
2018-06-19 18:31:30 +00:00
processed,
substepsInStep(Step::Dialogs));
2018-06-02 14:29:21 +00:00
});
}
ProcessingState ControllerObject::statePersonalInfo() const {
2018-06-18 21:52:13 +00:00
return prepareState(Step::PersonalInfo);
}
ProcessingState ControllerObject::stateUserpics(
2018-06-19 18:31:30 +00:00
const DownloadProgress &progress) const {
2018-06-18 21:52:13 +00:00
return prepareState(Step::Userpics, [&](ProcessingState &result) {
result.entityIndex = _userpicsWritten + progress.itemIndex;
result.entityCount = std::max(_userpicsCount, result.entityIndex);
2018-06-19 18:31:30 +00:00
result.bytesType = ProcessingState::FileType::Photo;
result.bytesRandomId = progress.randomId;
2018-06-19 18:31:30 +00:00
if (!progress.path.isEmpty()) {
const auto last = progress.path.lastIndexOf('/');
result.bytesName = progress.path.mid(last + 1);
}
result.bytesLoaded = progress.ready;
result.bytesCount = progress.total;
2018-06-02 14:29:21 +00:00
});
}
ProcessingState ControllerObject::stateContacts() const {
2018-06-18 21:52:13 +00:00
return prepareState(Step::Contacts);
}
ProcessingState ControllerObject::stateSessions() const {
2018-06-18 21:52:13 +00:00
return prepareState(Step::Sessions);
}
ProcessingState ControllerObject::stateOtherData() const {
2018-06-24 00:33:47 +00:00
return prepareState(Step::OtherData);
}
ProcessingState ControllerObject::stateDialogs(
2018-06-19 18:31:30 +00:00
const DownloadProgress &progress) const {
2018-06-18 21:52:13 +00:00
const auto step = Step::Dialogs;
return prepareState(step, [&](ProcessingState &result) {
2018-06-19 18:31:30 +00:00
fillMessagesState(
result,
_dialogsInfo,
_dialogIndex,
2018-07-11 21:06:35 +00:00
progress);
2018-06-02 14:29:21 +00:00
});
}
void ControllerObject::fillMessagesState(
2018-06-19 18:31:30 +00:00
ProcessingState &result,
const Data::DialogsInfo &info,
int index,
2018-07-11 21:06:35 +00:00
const DownloadProgress &progress) const {
const auto dialog = info.item(index);
Assert(dialog != nullptr);
result.entityIndex = index;
result.entityCount = info.chats.size() + info.left.size();
result.entityName = dialog->name;
result.entityType = (dialog->type == Data::DialogInfo::Type::Self)
? ProcessingState::EntityType::SavedMessages
2020-09-11 15:33:26 +00:00
: (dialog->type == Data::DialogInfo::Type::Replies)
? ProcessingState::EntityType::RepliesMessages
: ProcessingState::EntityType::Chat;
2018-06-19 18:31:30 +00:00
result.itemIndex = _messagesWritten + progress.itemIndex;
result.itemCount = std::max(_messagesCount, result.itemIndex);
2018-06-19 18:31:30 +00:00
result.bytesType = ProcessingState::FileType::File; // TODO
result.bytesRandomId = progress.randomId;
2018-06-19 18:31:30 +00:00
if (!progress.path.isEmpty()) {
const auto last = progress.path.lastIndexOf('/');
result.bytesName = progress.path.mid(last + 1);
}
result.bytesLoaded = progress.ready;
result.bytesCount = progress.total;
}
int ControllerObject::substepsInStep(Step step) const {
2018-06-19 18:31:30 +00:00
Expects(_substepsInStep.size() > static_cast<int>(step));
2018-06-18 21:52:13 +00:00
2018-06-19 18:31:30 +00:00
return _substepsInStep[static_cast<int>(step)];
2018-06-18 21:52:13 +00:00
}
void ControllerObject::setFinishedState() {
setState(FinishedState{
_writer->mainFilePath(),
_stats.filesCount(),
_stats.bytesCount() });
2018-06-18 21:52:13 +00:00
}
Controller::Controller(
QPointer<MTP::Instance> mtproto,
const MTPInputPeer &peer)
: _wrapped(std::move(mtproto), peer) {
2018-06-18 21:52:13 +00:00
}
rpl::producer<State> Controller::state() const {
return _wrapped.producer_on_main([=](const Implementation &unwrapped) {
return unwrapped.state();
2018-06-02 14:29:21 +00:00
});
}
//void Controller::submitPassword(const QString &password) {
// _wrapped.with([=](Implementation &unwrapped) {
// unwrapped.submitPassword(password);
2018-06-18 21:52:13 +00:00
// });
//}
//
//void Controller::requestPasswordRecover() {
// _wrapped.with([=](Implementation &unwrapped) {
// unwrapped.requestPasswordRecover();
2018-06-18 21:52:13 +00:00
// });
//}
//
//rpl::producer<PasswordUpdate> Controller::passwordUpdate() const {
// return _wrapped.producer_on_main([=](const Implementation &unwrapped) {
// return unwrapped.passwordUpdate();
2018-06-18 21:52:13 +00:00
// });
//}
//
//void Controller::reloadPasswordState() {
// _wrapped.with([=](Implementation &unwrapped) {
// unwrapped.reloadPasswordState();
2018-06-18 21:52:13 +00:00
// });
//}
//
//void Controller::cancelUnconfirmedPassword() {
// _wrapped.with([=](Implementation &unwrapped) {
// unwrapped.cancelUnconfirmedPassword();
2018-06-18 21:52:13 +00:00
// });
//}
void Controller::startExport(
2018-06-24 02:06:11 +00:00
const Settings &settings,
const Environment &environment) {
2018-06-02 14:29:21 +00:00
LOG(("Export Info: Started export to '%1'.").arg(settings.path));
_wrapped.with([=](Implementation &unwrapped) {
unwrapped.startExport(settings, environment);
2018-06-02 14:29:21 +00:00
});
}
void Controller::skipFile(uint64 randomId) {
_wrapped.with([=](Implementation &unwrapped) {
unwrapped.skipFile(randomId);
});
}
void Controller::cancelExportFast() {
2018-06-21 00:54:59 +00:00
LOG(("Export Info: Cancelled export."));
_wrapped.with([=](Implementation &unwrapped) {
unwrapped.cancelExportFast();
2018-06-21 00:54:59 +00:00
});
}
rpl::lifetime &Controller::lifetime() {
2018-06-02 14:29:21 +00:00
return _lifetime;
}
Controller::~Controller() {
2018-06-24 02:06:11 +00:00
LOG(("Export Info: Controller destroyed."));
}
2018-06-02 14:29:21 +00:00
} // namespace Export