Allow skipping stuck files in data export.

Fixes #6423.
This commit is contained in:
John Preston 2021-03-22 16:32:40 +04:00
parent 12db51fe75
commit 03a868a6e3
11 changed files with 150 additions and 22 deletions

View File

@ -2474,6 +2474,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_export_state_chats_list" = "Processing chats...";
"lng_export_state_chats" = "Chats";
"lng_export_state_ready_progress" = "{ready} / {total}";
"lng_export_skip_file" = "Skip this file";
"lng_export_progress" = "You can close this window now. Please don't quit Telegram until the data export is completed.";
"lng_export_stop" = "Stop";
"lng_export_sure_stop" = "Are you sure you want to stop exporting your data?\n\nIf you do, you'll need to start over.";

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_response.h"
#include "base/value_ordering.h"
#include "base/bytes.h"
#include "base/openssl_help.h"
#include <set>
#include <deque>
@ -182,6 +183,7 @@ struct ApiWrap::FileProcess {
Fn<bool(FileProgress)> progress;
FnMut<void(const QString &relativePath)> done;
uint64 randomId = 0;
Data::FileLocation location;
Data::FileOrigin origin;
int offset = 0;
@ -192,6 +194,7 @@ struct ApiWrap::FileProcess {
QByteArray bytes;
};
std::deque<Request> requests;
mtpRequestId requestId = 0;
};
struct ApiWrap::FileProgress {
@ -383,6 +386,7 @@ auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) {
Expects(location.dcId != 0
|| location.data.type() == mtpc_inputTakeoutFileLocation);
Expects(_takeoutId.has_value());
Expects(_fileProcess->requestId == 0);
return std::move(_mtp.request(MTPInvokeWithTakeout<MTPupload_GetFile>(
MTP_long(*_takeoutId),
@ -392,6 +396,7 @@ auto ApiWrap::fileRequest(const Data::FileLocation &location, int offset) {
MTP_int(offset),
MTP_int(kFileChunkSize))
)).fail([=](const MTP::Error &result) {
_fileProcess->requestId = 0;
if (result.type() == qstr("TAKEOUT_FILE_EMPTY")
&& _otherDataProcess != nullptr) {
filePartDone(
@ -853,6 +858,7 @@ bool ApiWrap::loadUserpicProgress(FileProgress progress) {
< _userpicsProcess->slice->list.size()));
return _userpicsProcess->fileProgress(DownloadProgress{
_fileProcess->randomId,
_fileProcess->relativePath,
_userpicsProcess->fileIndex,
progress.ready,
@ -1061,6 +1067,17 @@ void ApiWrap::finishExport(FnMut<void()> done) {
)).done(std::move(done)).send();
}
void ApiWrap::skipFile(uint64 randomId) {
if (!_fileProcess || _fileProcess->randomId != randomId) {
return;
}
LOG(("Export Info: File skipped."));
Assert(!_fileProcess->requests.empty());
Assert(_fileProcess->requestId != 0);
_mtp.request(base::take(_fileProcess->requestId)).cancel();
base::take(_fileProcess)->done(QString());
}
void ApiWrap::cancelExportFast() {
if (_takeoutId.has_value()) {
const auto requestId = mainRequest(MTPaccount_FinishTakeoutSession(
@ -1591,10 +1608,11 @@ bool ApiWrap::loadMessageFileProgress(FileProgress progress) {
&& (_chatProcess->fileIndex < _chatProcess->slice->list.size()));
return _chatProcess->fileProgress(DownloadProgress{
_fileProcess->relativePath,
_chatProcess->fileIndex,
progress.ready,
progress.total });
.randomId = _fileProcess->randomId,
.path = _fileProcess->relativePath,
.itemIndex = _chatProcess->fileIndex,
.ready = progress.ready,
.total = progress.total });
}
void ApiWrap::loadMessageFileDone(const QString &relativePath) {
@ -1740,6 +1758,8 @@ void ApiWrap::loadFile(
}
loadFilePart();
Ensures(_fileProcess->requestId != 0);
}
auto ApiWrap::prepareFileProcess(
@ -1758,11 +1778,13 @@ auto ApiWrap::prepareFileProcess(
result->location = file.location;
result->size = file.size;
result->origin = origin;
result->randomId = openssl::RandomValue<uint64>();
return result;
}
void ApiWrap::loadFilePart() {
if (!_fileProcess
|| _fileProcess->requestId
|| _fileProcess->requests.size() >= kFileRequestsCount
|| (_fileProcess->size > 0
&& _fileProcess->offset >= _fileProcess->size)) {
@ -1771,16 +1793,18 @@ void ApiWrap::loadFilePart() {
const auto offset = _fileProcess->offset;
_fileProcess->requests.push_back({ offset });
fileRequest(
_fileProcess->requestId = fileRequest(
_fileProcess->location,
_fileProcess->offset
).done([=](const MTPupload_File &result) {
_fileProcess->requestId = 0;
filePartDone(offset, result);
}).send();
_fileProcess->offset += kFileChunkSize;
if (_fileProcess->size > 0
&& _fileProcess->requests.size() < kFileRequestsCount) {
// Only one request at a time supported right now.
//const auto runner = _runner;
//crl::on_main([=] {
// QTimer::singleShot(kFileNextRequestDelay, [=] {
@ -1854,6 +1878,7 @@ void ApiWrap::filePartDone(int offset, const MTPupload_File &result) {
void ApiWrap::filePartRefreshReference(int offset) {
Expects(_fileProcess != nullptr);
Expects(_fileProcess->requestId == 0);
const auto &origin = _fileProcess->origin;
if (!origin.messageId) {
@ -1870,26 +1895,33 @@ void ApiWrap::filePartRefreshReference(int offset) {
origin.peer.c_inputPeerChannelFromMessage().vpeer(),
origin.peer.c_inputPeerChannelFromMessage().vmsg_id(),
origin.peer.c_inputPeerChannelFromMessage().vchannel_id());
mainRequest(MTPchannels_GetMessages(
_fileProcess->requestId = mainRequest(MTPchannels_GetMessages(
channel,
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(origin.messageId)))
)).fail([=](const MTP::Error &error) {
_fileProcess->requestId = 0;
filePartUnavailable();
return true;
}).done([=](const MTPmessages_Messages &result) {
_fileProcess->requestId = 0;
filePartExtractReference(offset, result);
}).send();
} else {
splitRequest(origin.split, MTPmessages_GetMessages(
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(origin.messageId)))
)).fail([=](const MTP::Error &error) {
_fileProcess->requestId = splitRequest(
origin.split,
MTPmessages_GetMessages(
MTP_vector<MTPInputMessage>(
1,
MTP_inputMessageID(MTP_int(origin.messageId)))
)
).fail([=](const MTP::Error &error) {
_fileProcess->requestId = 0;
filePartUnavailable();
return true;
}).done([=](const MTPmessages_Messages &result) {
_fileProcess->requestId = 0;
filePartExtractReference(offset, result);
}).send();
}
@ -1899,6 +1931,7 @@ void ApiWrap::filePartExtractReference(
int offset,
const MTPmessages_Messages &result) {
Expects(_fileProcess != nullptr);
Expects(_fileProcess->requestId == 0);
result.match([&](const MTPDmessages_messagesNotModified &data) {
error("Unexpected messagesNotModified received.");
@ -1922,10 +1955,11 @@ void ApiWrap::filePartExtractReference(
_fileProcess->location,
message.thumb().file.location);
if (refresh1 || refresh2) {
fileRequest(
_fileProcess->requestId = fileRequest(
_fileProcess->location,
offset
).done([=](const MTPupload_File &result) {
_fileProcess->requestId = 0;
filePartDone(offset, result);
}).send();
return;

View File

@ -60,6 +60,7 @@ public:
FnMut<void(Data::File&&)> done);
struct DownloadProgress {
uint64 randomId = 0;
QString path;
int itemIndex = 0;
int ready = 0;
@ -83,6 +84,7 @@ public:
FnMut<void()> done);
void finishExport(FnMut<void()> done);
void skipFile(uint64 randomId);
void cancelExportFast();
~ApiWrap();

View File

@ -51,12 +51,14 @@ public:
void startExport(
const Settings &settings,
const Environment &environment);
void skipFile(uint64 randomId);
void cancelExportFast();
private:
using Step = ProcessingState::Step;
using DownloadProgress = ApiWrap::DownloadProgress;
[[nodiscard]] bool stopped() const;
void setState(State &&state);
void ioError(const QString &path);
bool ioCatchError(Output::Result result);
@ -166,8 +168,15 @@ rpl::producer<State> ControllerObject::state() const {
});
}
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 (v::is<CancelledState>(_state)) {
if (stopped()) {
return;
}
_state = std::move(state);
@ -245,6 +254,13 @@ void ControllerObject::startExport(
exportNext();
}
void ControllerObject::skipFile(uint64 randomId) {
if (stopped()) {
return;
}
_api.skipFile(randomId);
}
void ControllerObject::fillExportSteps() {
using Type = Settings::Type;
_steps.push_back(Step::Initializing);
@ -518,6 +534,7 @@ ProcessingState ControllerObject::stateUserpics(
result.entityIndex = _userpicsWritten + progress.itemIndex;
result.entityCount = std::max(_userpicsCount, result.entityIndex);
result.bytesType = ProcessingState::FileType::Photo;
result.bytesRandomId = progress.randomId;
if (!progress.path.isEmpty()) {
const auto last = progress.path.lastIndexOf('/');
result.bytesName = progress.path.mid(last + 1);
@ -570,6 +587,7 @@ void ControllerObject::fillMessagesState(
result.itemIndex = _messagesWritten + progress.itemIndex;
result.itemCount = std::max(_messagesCount, result.itemIndex);
result.bytesType = ProcessingState::FileType::File; // TODO
result.bytesRandomId = progress.randomId;
if (!progress.path.isEmpty()) {
const auto last = progress.path.lastIndexOf('/');
result.bytesName = progress.path.mid(last + 1);
@ -643,6 +661,12 @@ void Controller::startExport(
});
}
void Controller::skipFile(uint64 randomId) {
_wrapped.with([=](Implementation &unwrapped) {
unwrapped.skipFile(randomId);
});
}
void Controller::cancelExportFast() {
LOG(("Export Info: Cancelled export."));

View File

@ -74,6 +74,7 @@ struct ProcessingState {
int itemIndex = 0;
int itemCount = 0;
uint64 bytesRandomId = 0;
FileType bytesType = FileType::None;
QString bytesName;
int bytesLoaded = 0;
@ -136,6 +137,7 @@ public:
void startExport(
const Settings &settings,
const Environment &environment);
void skipFile(uint64 randomId);
void cancelExportFast();
rpl::lifetime &lifetime();

View File

@ -50,7 +50,8 @@ exportErrorLabel: FlatLabel(boxLabel) {
exportProgressDuration: 200;
exportProgressRowHeight: 30px;
exportProgressRowPadding: margins(22px, 10px, 22px, 20px);
exportProgressRowPadding: margins(22px, 10px, 22px, 10px);
exportProgressRowSkip: 10px;
exportProgressLabel: FlatLabel(boxLabel) {
textFg: windowBoldFg;
maxHeight: 20px;

View File

@ -26,8 +26,9 @@ Content ContentFromState(
const QString &id,
const QString &label,
const QString &info,
float64 progress) {
result.rows.push_back({ id, label, info, progress });
float64 progress,
uint64 randomId = 0) {
result.rows.push_back({ id, label, info, progress, randomId });
};
const auto pushMain = [&](const QString &label) {
const auto info = (state.entityCount > 0)
@ -56,7 +57,10 @@ Content ContentFromState(
: addPart(state.entityIndex, state.entityCount);
push("main", label, info, doneProgress + addProgress);
};
const auto pushBytes = [&](const QString &id, const QString &label) {
const auto pushBytes = [&](
const QString &id,
const QString &label,
uint64 randomId) {
if (!state.bytesCount) {
return;
}
@ -64,7 +68,7 @@ Content ContentFromState(
const auto info = Ui::FormatDownloadText(
state.bytesLoaded,
state.bytesCount);
push(id, label, info, progress);
push(id, label, info, progress, randomId);
};
switch (state.step) {
case Step::Initializing:
@ -80,7 +84,8 @@ Content ContentFromState(
pushMain(tr::lng_export_state_userpics(tr::now));
pushBytes(
"userpic" + QString::number(state.entityIndex),
state.bytesName);
state.bytesName,
state.bytesRandomId);
break;
case Step::Contacts:
pushMain(tr::lng_export_option_contacts(tr::now));
@ -117,7 +122,8 @@ Content ContentFromState(
+ QString::number(state.entityIndex)
+ '_'
+ QString::number(state.itemIndex)),
state.bytesName);
state.bytesName,
state.bytesRandomId);
break;
default: Unexpected("Step in ContentFromState.");
}

View File

@ -22,6 +22,7 @@ struct Content {
QString label;
QString info;
float64 progress = 0.;
uint64 randomId = 0;
};
std::vector<Row> rows;

View File

@ -299,6 +299,11 @@ void PanelController::showProgress() {
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();

View File

@ -18,6 +18,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export {
namespace View {
namespace {
constexpr auto kShowSkipFileTimeout = 5 * crl::time(1000);
} // namespace
class ProgressWidget::Row : public Ui::RpWidget {
public:
@ -235,13 +240,26 @@ ProgressWidget::ProgressWidget(
QWidget *parent,
rpl::producer<Content> content)
: RpWidget(parent)
, _body(this) {
, _body(this)
, _fileShowSkipTimer([=] { _skipFile->show(anim::type::normal); }) {
widthValue(
) | rpl::start_with_next([=](int width) {
_body->resizeToWidth(width);
_body->moveToLeft(0, 0);
}, _body->lifetime());
auto skipFileWrap = _body->add(object_ptr<Ui::FixedHeightWidget>(
_body.data(),
st::defaultLinkButton.font->height + st::exportProgressRowSkip));
_skipFile = base::make_unique_q<Ui::FadeWrap<Ui::LinkButton>>(
skipFileWrap,
object_ptr<Ui::LinkButton>(
this,
tr::lng_export_skip_file(tr::now),
st::defaultLinkButton));
_skipFile->hide(anim::type::instant);
_skipFile->moveToLeft(st::exportProgressRowPadding.left(), 0);
_about = _body->add(
object_ptr<Ui::FlatLabel>(
this,
@ -262,6 +280,11 @@ ProgressWidget::ProgressWidget(
setupBottomButton(_cancel.get());
}
rpl::producer<uint64> ProgressWidget::skipFileClicks() const {
return _skipFile->entity()->clicks(
) | rpl::map([=] { return _fileRandomId; });
}
rpl::producer<> ProgressWidget::cancelClicks() const {
return _cancel
? (_cancel->clicks() | rpl::to_empty)
@ -294,14 +317,32 @@ void ProgressWidget::updateState(Content &&content) {
if (index < _rows.size()) {
_rows[index]->updateData(std::move(row));
} else {
if (index > 0) {
_body->insert(
index * 2 - 1,
object_ptr<Ui::FixedHeightWidget>(
this,
st::exportProgressRowSkip));
}
_rows.push_back(_body->insert(
index,
index * 2,
object_ptr<Row>(this, std::move(row)),
st::exportProgressRowPadding));
_rows.back()->show();
}
++index;
}
const auto fileRandomId = !content.rows.empty()
? content.rows.back().randomId
: uint64(0);
if (_fileRandomId != fileRandomId) {
_fileShowSkipTimer.cancel();
_skipFile->hide(anim::type::normal);
_fileRandomId = fileRandomId;
if (_fileRandomId) {
_fileShowSkipTimer.callOnce(kShowSkipFileTimeout);
}
}
for (const auto count = _rows.size(); index != count; ++index) {
_rows[index]->updateData(Content::Row());
}
@ -312,6 +353,8 @@ void ProgressWidget::updateState(Content &&content) {
void ProgressWidget::showDone() {
_cancel = nullptr;
_skipFile->hide(anim::type::instant);
_fileShowSkipTimer.cancel();
_about->setText(tr::lng_export_about_done(tr::now));
_done = base::make_unique_q<Ui::RoundButton>(
this,

View File

@ -10,11 +10,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h"
#include "export/view/export_view_content.h"
#include "base/object_ptr.h"
#include "base/timer.h"
namespace Ui {
class VerticalLayout;
class RoundButton;
class FlatLabel;
class LinkButton;
template <typename Widget>
class FadeWrap;
} // namespace Ui
namespace Export {
@ -26,6 +30,7 @@ public:
QWidget *parent,
rpl::producer<Content> content);
rpl::producer<uint64> skipFileClicks() const;
rpl::producer<> cancelClicks() const;
rpl::producer<> doneClicks() const;
@ -42,11 +47,15 @@ private:
object_ptr<Ui::VerticalLayout> _body;
std::vector<not_null<Row*>> _rows;
base::unique_qptr<Ui::FadeWrap<Ui::LinkButton>> _skipFile;
QPointer<Ui::FlatLabel> _about;
base::unique_qptr<Ui::RoundButton> _cancel;
base::unique_qptr<Ui::RoundButton> _done;
rpl::event_stream<> _doneClicks;
uint64 _fileRandomId = 0;
base::Timer _fileShowSkipTimer;
};
} // namespace View