Display errors in export UI.

All errors are now fatal errors :(
This commit is contained in:
John Preston 2018-06-19 11:42:21 +01:00
parent 7d4e23448e
commit 5f01751660
23 changed files with 844 additions and 284 deletions

View File

@ -1654,7 +1654,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_passport_bad_name" = "Please use latin characters only.";
"lng_export_title" = "Personal data export";
"lng_export_option_info" = "Personal info";
"lng_export_option_info" = "Personal information";
"lng_export_option_contacts" = "Contacts list";
"lng_export_option_sessions" = "Sessions list";
"lng_export_header_chats" = "Chats export settings";
@ -1675,6 +1675,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_export_option_files" = "Files";
"lng_export_option_size_limit" = "Size limit: {size}";
"lng_export_start" = "Export";
"lng_export_state_initializing" = "Initializing...";
"lng_export_state_userpics" = "Personal photos";
"lng_export_state_chats_list" = "Processing chats...";
"lng_export_state_chats" = "Chats";
"lng_export_state_progress" = "{count} / {total}";
"lng_export_state_photo" = "Photo";
"lng_export_state_video_file" = "Video file";
"lng_export_state_voice_message" = "Voice message";
"lng_export_state_video_message" = "Round video message";
"lng_export_state_sticker" = "Sticker";
"lng_export_state_gif" = "Animated GIF";
// Wnd specific

View File

@ -250,6 +250,21 @@ QString CleanDocumentName(QString name) {
return name;
}
QString DocumentFolder(const Document &data) {
if (data.isVideoFile) {
return "VideoFiles";
} else if (data.isAnimated) {
return "AnimatedGIFs";
} else if (data.isSticker) {
return "Stickers";
} else if (data.isVoiceMessage) {
return "VoiceMessages";
} else if (data.isVideoMessage) {
return "RoundVideoMessages";
}
return "Files";
}
Document ParseDocument(
const MTPDocument &data,
const QString &suggestedFolder,
@ -267,6 +282,7 @@ Document ParseDocument(
result.mime = ParseString(data.vmime_type);
ParseAttributes(result, data.vattributes);
result.file.suggestedPath = suggestedFolder
+ DocumentFolder(result) + '/'
+ CleanDocumentName(
ComputeDocumentName(result, date ? date : result.date));
}, [&](const MTPDdocumentEmpty &data) {
@ -548,10 +564,7 @@ Media ParseMedia(
result.content = UnsupportedMedia();
}, [&](const MTPDmessageMediaDocument &data) {
result.content = data.has_document()
? ParseDocument(
data.vdocument,
folder + "Files/",
date)
? ParseDocument(data.vdocument, folder, date)
: Document();
if (data.has_ttl_seconds()) {
result.ttl = data.vttl_seconds.v;

View File

@ -427,6 +427,9 @@ struct DialogInfo {
bool onlyMyMessages = false;
QString relativePath;
// Filled when requesting dialog messages.
int messagesCount = 0;
};
struct DialogsInfo {

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "export/export_settings.h"
#include "export/data/export_data_types.h"
#include "export/output/export_output_result.h"
#include "export/output/export_output_file.h"
#include "mtproto/rpc_sender.h"
#include "base/value_ordering.h"
@ -114,11 +115,13 @@ struct ApiWrap::StartProcess {
};
struct ApiWrap::UserpicsProcess {
FnMut<void(Data::UserpicsInfo&&)> start;
Fn<void(Data::UserpicsSlice&&)> handleSlice;
FnMut<bool(Data::UserpicsInfo&&)> start;
Fn<bool(DownloadProgress)> fileProgress;
Fn<bool(Data::UserpicsSlice&&)> handleSlice;
FnMut<void()> finish;
base::optional<Data::UserpicsSlice> slice;
uint64 maxId = 0;
bool lastSlice = false;
int fileIndex = -1;
@ -130,6 +133,7 @@ struct ApiWrap::FileProcess {
Output::File file;
QString relativePath;
Fn<bool(FileProgress)> progress;
FnMut<void(const QString &relativePath)> done;
Data::FileLocation location;
@ -144,18 +148,25 @@ struct ApiWrap::FileProcess {
};
struct ApiWrap::FileProgress {
int ready = 0;
int total = 0;
};
struct ApiWrap::LeftChannelsProcess {
Fn<bool(int count)> progress;
FnMut<void(Data::DialogsInfo&&)> done;
Data::DialogsInfo info;
rpl::variable<int> count;
int fullCount = 0;
int offset = 0;
bool finished = false;
};
struct ApiWrap::DialogsProcess {
Fn<bool(int count)> progress;
FnMut<void(Data::DialogsInfo&&)> done;
Data::DialogsInfo info;
@ -164,14 +175,14 @@ struct ApiWrap::DialogsProcess {
int32 offsetId = 0;
MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
rpl::variable<int> count;
};
struct ApiWrap::ChatProcess {
Data::DialogInfo info;
Fn<void(Data::MessagesSlice&&)> handleSlice;
FnMut<bool(const Data::DialogInfo &)> start;
Fn<bool(DownloadProgress)> fileProgress;
Fn<bool(Data::MessagesSlice&&)> handleSlice;
FnMut<void()> done;
int32 offsetId = 1;
@ -247,6 +258,10 @@ rpl::producer<RPCError> ApiWrap::errors() const {
return _errors.events();
}
rpl::producer<Output::Result> ApiWrap::ioErrors() const {
return _ioErrors.events();
}
void ApiWrap::startExport(
const Settings &settings,
FnMut<void(StartInfo)> done) {
@ -260,9 +275,11 @@ void ApiWrap::startExport(
using Step = StartProcess::Step;
if (_settings->types & Settings::Type::Userpics) {
_startProcess->steps.push_back(Step::UserpicsCount);
} else if (_settings->types & Settings::Type::AnyChatsMask) {
}
if (_settings->types & Settings::Type::AnyChatsMask) {
_startProcess->steps.push_back(Step::DialogsCount);
} else if (_settings->types & Settings::Type::GroupsChannelsMask) {
}
if (_settings->types & Settings::Type::GroupsChannelsMask) {
_startProcess->steps.push_back(Step::LeftChannelsCount);
}
startMainSession([=] {
@ -361,9 +378,11 @@ void ApiWrap::finishStartProcess() {
}
void ApiWrap::requestLeftChannelsList(
Fn<bool(int count)> progress,
FnMut<void(Data::DialogsInfo&&)> done) {
Expects(_leftChannelsProcess != nullptr);
_leftChannelsProcess->progress = std::move(progress);
_leftChannelsProcess->done = std::move(done);
requestLeftChannelsSlice();
}
@ -382,27 +401,18 @@ void ApiWrap::requestLeftChannelsSlice() {
});
}
rpl::producer<int> ApiWrap::leftChannelsLoadedCount() const {
Expects(_leftChannelsProcess != nullptr);
return _leftChannelsProcess->count.value();
}
void ApiWrap::requestDialogsList(FnMut<void(Data::DialogsInfo&&)> done) {
void ApiWrap::requestDialogsList(
Fn<bool(int count)> progress,
FnMut<void(Data::DialogsInfo&&)> done) {
Expects(_dialogsProcess == nullptr);
_dialogsProcess = std::make_unique<DialogsProcess>();
_dialogsProcess->progress = std::move(progress);
_dialogsProcess->done = std::move(done);
requestDialogsSlice();
}
rpl::producer<int> ApiWrap::dialogsLoadedCount() const {
Expects(_dialogsProcess != nullptr);
return _dialogsProcess->count.value();
}
void ApiWrap::startMainSession(FnMut<void()> done) {
const auto sizeLimit = _settings->media.sizeLimit;
const auto hasFiles = (_settings->media.types != 0) && (sizeLimit > 0);
@ -456,33 +466,35 @@ void ApiWrap::requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done) {
}
void ApiWrap::requestUserpics(
FnMut<void(Data::UserpicsInfo&&)> start,
Fn<void(Data::UserpicsSlice&&)> slice,
FnMut<bool(Data::UserpicsInfo&&)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::UserpicsSlice&&)> slice,
FnMut<void()> finish) {
Expects(_userpicsProcess == nullptr);
_userpicsProcess = std::make_unique<UserpicsProcess>();
_userpicsProcess->start = std::move(start);
_userpicsProcess->fileProgress = std::move(progress);
_userpicsProcess->handleSlice = std::move(slice);
_userpicsProcess->finish = std::move(finish);
mainRequest(MTPphotos_GetUserPhotos(
_user,
MTP_int(0), // offset
MTP_long(0), // max_id
MTP_int(0), // offset
MTP_long(_userpicsProcess->maxId),
MTP_int(kUserpicsSliceLimit)
)).done([=](const MTPphotos_Photos &result) mutable {
Expects(_userpicsProcess != nullptr);
_userpicsProcess->start([&] {
auto info = Data::UserpicsInfo();
result.match([&](const MTPDphotos_photos &data) {
info.count = data.vphotos.v.size();
}, [&](const MTPDphotos_photosSlice &data) {
info.count = data.vcount.v;
});
return info;
}());
auto startInfo = result.match(
[](const MTPDphotos_photos &data) {
return Data::UserpicsInfo{ data.vphotos.v.size() };
}, [](const MTPDphotos_photosSlice &data) {
return Data::UserpicsInfo{ data.vcount.v };
});
if (!_userpicsProcess->start(std::move(startInfo))) {
return;
}
handleUserpicsSlice(result);
}).send();
@ -523,34 +535,54 @@ void ApiWrap::loadNextUserpic() {
}
const auto ready = processFileLoad(
list[index].image.file,
[=](FileProgress value) { return loadUserpicProgress(value); },
[=](const QString &path) { loadUserpicDone(path); });
if (!ready) {
return;
}
}
const auto lastUserpicId = list.empty()
? base::none
: base::make_optional(list.back().id);
finishUserpicsSlice();
}
if (!list.empty()) {
_userpicsProcess->handleSlice(*base::take(_userpicsProcess->slice));
void ApiWrap::finishUserpicsSlice() {
Expects(_userpicsProcess != nullptr);
Expects(_userpicsProcess->slice.has_value());
auto slice = *base::take(_userpicsProcess->slice);
if (!slice.list.empty()) {
_userpicsProcess->maxId = slice.list.back().id;
if (!_userpicsProcess->handleSlice(std::move(slice))) {
return;
}
}
if (_userpicsProcess->lastSlice) {
finishUserpics();
return;
}
Assert(lastUserpicId.has_value());
mainRequest(MTPphotos_GetUserPhotos(
_user,
MTP_int(0),
MTP_long(*lastUserpicId),
MTP_int(0), // offset
MTP_long(_userpicsProcess->maxId),
MTP_int(kUserpicsSliceLimit)
)).done([=](const MTPphotos_Photos &result) {
handleUserpicsSlice(result);
}).send();
}
bool ApiWrap::loadUserpicProgress(FileProgress progress) {
Expects(_userpicsProcess != nullptr);
Expects(_userpicsProcess->slice.has_value());
Expects((_userpicsProcess->fileIndex >= 0)
&& (_userpicsProcess->fileIndex
< _userpicsProcess->slice->list.size()));
return _userpicsProcess->fileProgress(DownloadProgress{
_userpicsProcess->fileIndex,
progress.ready,
progress.total });
}
void ApiWrap::loadUserpicDone(const QString &relativePath) {
Expects(_userpicsProcess != nullptr);
Expects(_userpicsProcess->slice.has_value());
@ -588,16 +620,25 @@ void ApiWrap::requestSessions(FnMut<void(Data::SessionsList&&)> done) {
void ApiWrap::requestMessages(
const Data::DialogInfo &info,
Fn<void(Data::MessagesSlice&&)> slice,
FnMut<bool(const Data::DialogInfo &)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::MessagesSlice&&)> slice,
FnMut<void()> done) {
Expects(_chatProcess == nullptr);
_chatProcess = std::make_unique<ChatProcess>();
_chatProcess->info = info;
_chatProcess->start = std::move(start);
_chatProcess->fileProgress = std::move(progress);
_chatProcess->handleSlice = std::move(slice);
_chatProcess->done = std::move(done);
requestMessagesSlice();
requestMessagesSlice([=](int count) {
Expects(_chatProcess != nullptr);
_chatProcess->info.messagesCount = count;
return _chatProcess->start(_chatProcess->info);
});
}
void ApiWrap::requestDialogsSlice() {
@ -628,6 +669,11 @@ void ApiWrap::requestDialogsSlice() {
appendDialogsSlice(std::move(info));
const auto count = _dialogsProcess->info.list.size();
if (!_dialogsProcess->progress(count)) {
return;
}
requestDialogsSlice();
}
}).send();
@ -654,7 +700,7 @@ void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
Expects(_leftChannelsProcess != nullptr);
mainRequest(MTPchannels_GetLeftChannels(
MTP_int(_leftChannelsProcess->info.list.size())
MTP_int(_leftChannelsProcess->offset)
)).done([=, done = std::move(done)](
const MTPmessages_Chats &result) mutable {
Expects(_leftChannelsProcess != nullptr);
@ -662,6 +708,11 @@ void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
appendLeftChannelsSlice(Data::ParseLeftChannelsInfo(result));
const auto process = _leftChannelsProcess.get();
process->offset += result.match(
[](const auto &data) {
return int(data.vchats.v.size());
});
process->fullCount = result.match(
[](const MTPDmessages_chats &data) {
return int(data.vchats.v.size());
@ -676,7 +727,11 @@ void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
return data.vchats.v.isEmpty();
});
process->count = process->info.list.size();
if (process->progress) {
if (!process->progress(process->info.list.size())) {
return;
}
}
done();
}).send();
@ -710,23 +765,30 @@ void ApiWrap::appendChatsSlice(
}
}
void ApiWrap::requestMessagesSlice() {
void ApiWrap::requestMessagesSlice(FnMut<bool(int count)> start) {
Expects(_chatProcess != nullptr);
// #TODO export
if (_chatProcess->info.input.match([](const MTPDinputPeerUser &value) {
return !value.vaccess_hash.v;
}, [](const auto &data) { return false; })) {
finishMessages();
return;
}
auto handleResult = [=](const MTPmessages_Messages &result) mutable {
auto handleResult = [=, start = std::move(start)](
const MTPmessages_Messages &result) mutable {
Expects(_chatProcess != nullptr);
const auto count = result.match(
[](const MTPDmessages_messages &data) {
return data.vmessages.v.size();
}, [](const MTPDmessages_messagesSlice &data) {
return data.vcount.v;
}, [](const MTPDmessages_channelMessages &data) {
return data.vcount.v;
}, [](const MTPDmessages_messagesNotModified &data) {
return 0;
});
result.match([&](const MTPDmessages_messagesNotModified &data) {
error("Unexpected messagesNotModified received.");
}, [&](const auto &data) {
if (start && !start(count)) {
return;
}
if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
_chatProcess->lastSlice = true;
}
@ -790,8 +852,12 @@ void ApiWrap::loadNextMessageFile() {
if (index >= list.size()) {
break;
}
const auto fileProgress = [=](FileProgress value) {
return loadMessageFileProgress(value);
};
const auto ready = processFileLoad(
list[index].file(),
fileProgress,
[=](const QString &path) { loadMessageFileDone(path); },
&list[index]);
if (!ready) {
@ -808,7 +874,9 @@ void ApiWrap::finishMessagesSlice() {
auto slice = *base::take(_chatProcess->slice);
if (!slice.list.empty()) {
_chatProcess->offsetId = slice.list.back().id + 1;
_chatProcess->handleSlice(std::move(slice));
if (!_chatProcess->handleSlice(std::move(slice))) {
return;
}
}
if (_chatProcess->lastSlice) {
finishMessages();
@ -817,6 +885,18 @@ void ApiWrap::finishMessagesSlice() {
}
}
bool ApiWrap::loadMessageFileProgress(FileProgress progress) {
Expects(_chatProcess != nullptr);
Expects(_chatProcess->slice.has_value());
Expects((_chatProcess->fileIndex >= 0)
&& (_chatProcess->fileIndex < _chatProcess->slice->list.size()));
return _chatProcess->fileProgress(DownloadProgress{
_chatProcess->fileIndex,
progress.ready,
progress.total });
}
void ApiWrap::loadMessageFileDone(const QString &relativePath) {
Expects(_chatProcess != nullptr);
Expects(_chatProcess->slice.has_value());
@ -838,6 +918,7 @@ void ApiWrap::finishMessages() {
bool ApiWrap::processFileLoad(
Data::File &file,
Fn<bool(FileProgress)> progress,
FnMut<void(QString)> done,
Data::Message *message) {
using SkipReason = Data::File::SkipReason;
@ -848,7 +929,7 @@ bool ApiWrap::processFileLoad(
file.skipReason = SkipReason::Unavailable;
return true;
} else if (writePreloadedFile(file)) {
return true;
return !file.relativePath.isEmpty();
}
using Type = MediaSettings::Type;
@ -878,7 +959,7 @@ bool ApiWrap::processFileLoad(
file.skipReason = SkipReason::FileSize;
return true;
}
loadFile(file, std::move(done));
loadFile(file, std::move(progress), std::move(done));
return false;
}
@ -893,25 +974,39 @@ bool ApiWrap::writePreloadedFile(Data::File &file) {
} else if (!file.content.isEmpty()) {
const auto process = prepareFileProcess(file);
auto &output = _fileProcess->file;
if (output.writeBlock(file.content) == File::Result::Success) {
if (const auto result = output.writeBlock(file.content)) {
file.relativePath = process->relativePath;
_fileCache->save(file.location, file.relativePath);
return true;
} else {
ioError(result);
}
error(QString("Could not write '%1'.").arg(process->relativePath));
return true;
}
return false;
}
void ApiWrap::loadFile(
const Data::File &file,
Fn<bool(FileProgress)> progress,
FnMut<void(QString)> done) {
Expects(_fileProcess == nullptr);
Expects(file.location.dcId != 0);
_fileProcess = prepareFileProcess(file);
_fileProcess->progress = std::move(progress);
_fileProcess->done = std::move(done);
if (_fileProcess->progress) {
const auto progress = FileProgress{
_fileProcess->file.size(),
_fileProcess->size
};
if (!_fileProcess->progress(progress)) {
return;
}
}
loadFilePart();
}
@ -989,14 +1084,19 @@ void ApiWrap::filePartDone(int offset, const MTPupload_File &result) {
auto &file = _fileProcess->file;
while (!requests.empty() && !requests.front().bytes.isEmpty()) {
const auto &bytes = requests.front().bytes;
if (file.writeBlock(bytes) != Output::File::Result::Success) {
error(QString("Could not write bytes to '%1'."
).arg(_fileProcess->relativePath));
if (const auto result = file.writeBlock(bytes); !result) {
ioError(result);
return;
}
requests.pop_front();
}
if (_fileProcess->progress) {
_fileProcess->progress(FileProgress{
file.size(),
_fileProcess->size });
}
if (!requests.empty()
|| !_fileProcess->size
|| _fileProcess->size > _fileProcess->offset) {
@ -1019,6 +1119,10 @@ void ApiWrap::error(const QString &text) {
error(MTP_rpc_error(MTP_int(0), MTP_string("API_ERROR: " + text)));
}
void ApiWrap::ioError(const Output::Result &result) {
_ioErrors.fire_copy(result);
}
ApiWrap::~ApiWrap() = default;
} // namespace Export

View File

@ -25,6 +25,10 @@ struct MessagesSlice;
struct Message;
} // namespace Data
namespace Output {
struct Result;
} // namespace Output
struct Settings;
class ApiWrap {
@ -32,6 +36,7 @@ public:
ApiWrap(Fn<void(FnMut<void()>)> runner);
rpl::producer<RPCError> errors() const;
rpl::producer<Output::Result> ioErrors() const;
struct StartInfo {
int userpicsCount = 0;
@ -42,17 +47,24 @@ public:
const Settings &settings,
FnMut<void(StartInfo)> done);
void requestLeftChannelsList(FnMut<void(Data::DialogsInfo&&)> done);
rpl::producer<int> leftChannelsLoadedCount() const;
void requestDialogsList(FnMut<void(Data::DialogsInfo&&)> done);
rpl::producer<int> dialogsLoadedCount() const;
void requestLeftChannelsList(
Fn<bool(int count)> progress,
FnMut<void(Data::DialogsInfo&&)> done);
void requestDialogsList(
Fn<bool(int count)> progress,
FnMut<void(Data::DialogsInfo&&)> done);
void requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done);
struct DownloadProgress {
int itemIndex = 0;
int ready = 0;
int total = 0;
};
void requestUserpics(
FnMut<void(Data::UserpicsInfo&&)> start,
Fn<void(Data::UserpicsSlice&&)> slice,
FnMut<bool(Data::UserpicsInfo&&)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::UserpicsSlice&&)> slice,
FnMut<void()> finish);
void requestContacts(FnMut<void(Data::ContactsList&&)> done);
@ -61,7 +73,9 @@ public:
void requestMessages(
const Data::DialogInfo &info,
Fn<void(Data::MessagesSlice&&)> slice,
FnMut<bool(const Data::DialogInfo &)> start,
Fn<bool(DownloadProgress)> progress,
Fn<bool(Data::MessagesSlice&&)> slice,
FnMut<void()> done);
~ApiWrap();
@ -71,6 +85,7 @@ private:
struct StartProcess;
struct UserpicsProcess;
struct FileProcess;
struct FileProgress;
struct LeftChannelsProcess;
struct DialogsProcess;
struct ChatProcess;
@ -85,7 +100,9 @@ private:
void handleUserpicsSlice(const MTPphotos_Photos &result);
void loadUserpicsFiles(Data::UserpicsSlice &&slice);
void loadNextUserpic();
bool loadUserpicProgress(FileProgress value);
void loadUserpicDone(const QString &relativePath);
void finishUserpicsSlice();
void finishUserpics();
void requestDialogsSlice();
@ -98,15 +115,17 @@ private:
void appendChatsSlice(Data::DialogsInfo &to, Data::DialogsInfo &&info);
void requestMessagesSlice();
void requestMessagesSlice(FnMut<bool(int count)> start = nullptr);
void loadMessagesFiles(Data::MessagesSlice &&slice);
void loadNextMessageFile();
bool loadMessageFileProgress(FileProgress value);
void loadMessageFileDone(const QString &relativePath);
void finishMessagesSlice();
void finishMessages();
bool processFileLoad(
Data::File &file,
Fn<bool(FileProgress)> progress,
FnMut<void(QString)> done,
Data::Message *message = nullptr);
std::unique_ptr<FileProcess> prepareFileProcess(
@ -114,6 +133,7 @@ private:
bool writePreloadedFile(Data::File &file);
void loadFile(
const Data::File &file,
Fn<bool(FileProgress)> progress,
FnMut<void(QString)> done);
void loadFilePart();
void filePartDone(int offset, const MTPupload_File &result);
@ -127,6 +147,7 @@ private:
void error(RPCError &&error);
void error(const QString &text);
void ioError(const Output::Result &result);
MTP::ConcurrentSender _mtp;
base::optional<uint64> _takeoutId;
@ -143,6 +164,7 @@ private:
std::unique_ptr<ChatProcess> _chatProcess;
rpl::event_stream<RPCError> _errors;
rpl::event_stream<Output::Result> _ioErrors;
};

View File

@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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"
namespace Export {
@ -34,9 +35,11 @@ public:
private:
using Step = ProcessingState::Step;
using DownloadProgress = ApiWrap::DownloadProgress;
void setState(State &&state);
void ioError(const QString &path);
bool ioCatchError(Output::Result result);
void setFinishedState();
//void requestPasswordState();
@ -65,11 +68,11 @@ private:
ProcessingState stateLeftChannelsList(int processed) const;
ProcessingState stateDialogsList(int processed) const;
ProcessingState statePersonalInfo() const;
ProcessingState stateUserpics() const;
ProcessingState stateUserpics(DownloadProgress progress) const;
ProcessingState stateContacts() const;
ProcessingState stateSessions() const;
ProcessingState stateLeftChannels() const;
ProcessingState stateDialogs() const;
ProcessingState stateLeftChannels(DownloadProgress progress) const;
ProcessingState stateDialogs(DownloadProgress progress) const;
int substepsInStep(Step step) const;
@ -77,15 +80,23 @@ private:
ApiWrap _api;
Settings _settings;
Data::DialogsInfo _leftChannelsInfo;
Data::DialogsInfo _dialogsInfo;
int _leftChannelIndex = -1;
Data::DialogsInfo _dialogsInfo;
int _dialogIndex = -1;
int _messagesWritten = 0;
int _messagesCount = 0;
int _userpicsWritten = 0;
int _userpicsCount = 0;
// rpl::variable<State> fails to compile in MSVC :(
State _state;
rpl::event_stream<State> _stateChanges;
std::vector<int> _substepsInStep;
std::shared_ptr<const std::vector<int>> _substepsInStep;
std::unique_ptr<Output::AbstractWriter> _writer;
std::vector<Step> _steps;
@ -100,7 +111,12 @@ Controller::Controller(crl::weak_on_queue<Controller> weak)
, _state(PasswordCheckState{}) {
_api.errors(
) | rpl::start_with_next([=](RPCError &&error) {
setState(ErrorState{ ErrorState::Type::API, std::move(error) });
setState(ApiErrorState{ std::move(error) });
}, _lifetime);
_api.ioErrors(
) | rpl::start_with_next([=](const Output::Result &result) {
ioCatchError(result);
}, _lifetime);
//requestPasswordState();
@ -127,7 +143,15 @@ void Controller::setState(State &&state) {
}
void Controller::ioError(const QString &path) {
setState(ErrorState{ ErrorState::Type::IO, base::none, path });
setState(OutputErrorState{ path });
}
bool Controller::ioCatchError(Output::Result result) {
if (!result) {
ioError(result.path);
return true;
}
return false;
}
//void Controller::submitPassword(const QString &password) {
@ -223,7 +247,7 @@ void Controller::fillExportSteps() {
using Type = Settings::Type;
_steps.push_back(Step::Initializing);
if (_settings.types & Type::GroupsChannelsMask) {
_steps.push_back(Step::LeftChannels);
_steps.push_back(Step::LeftChannelsList);
}
if (_settings.types & Type::AnyChatsMask) {
_steps.push_back(Step::DialogsList);
@ -243,15 +267,19 @@ void Controller::fillExportSteps() {
if (_settings.types & Type::AnyChatsMask) {
_steps.push_back(Step::Dialogs);
}
if (_settings.types & Type::GroupsChannelsMask) {
_steps.push_back(Step::LeftChannels);
}
}
void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
auto result = std::vector<int>();
const auto push = [&](Step step, int count) {
const auto index = static_cast<int>(step);
if (index >= _substepsInStep.size()) {
_substepsInStep.resize(index + 1, 0);
if (index >= result.size()) {
result.resize(index + 1, 0);
}
_substepsInStep[index] = count;
result[index] = count;
};
push(Step::Initializing, 1);
if (_settings.types & Settings::Type::GroupsChannelsMask) {
@ -278,14 +306,20 @@ void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
if (_settings.types & Settings::Type::AnyChatsMask) {
push(Step::Dialogs, info.dialogsCount);
}
_substepsInStep = std::make_shared<const std::vector<int>>(
std::move(result));
}
void Controller::exportNext() {
if (!++_stepIndex) {
_writer->start(_settings);
if (ioCatchError(_writer->start(_settings))) {
return;
}
}
if (_stepIndex >= _steps.size()) {
_writer->finish();
if (ioCatchError(_writer->finish())) {
return;
}
setFinishedState();
return;
}
@ -314,63 +348,82 @@ void Controller::initialize() {
}
void Controller::collectLeftChannels() {
_api.requestLeftChannelsList([=](Data::DialogsInfo &&result) {
_api.requestLeftChannelsList([=](int count) {
setState(stateLeftChannelsList(count));
return true;
}, [=](Data::DialogsInfo &&result) {
_leftChannelsInfo = std::move(result);
exportNext();
});
_api.leftChannelsLoadedCount(
) | rpl::start_with_next([=](int count) {
setState(stateLeftChannelsList(count));
}, _lifetime);
}
void Controller::collectDialogsList() {
_api.requestDialogsList([=](Data::DialogsInfo &&result) {
_api.requestDialogsList([=](int count) {
setState(stateDialogsList(count));
return true;
}, [=](Data::DialogsInfo &&result) {
_dialogsInfo = std::move(result);
exportNext();
});
_api.dialogsLoadedCount(
) | rpl::start_with_next([=](int count) {
setState(stateDialogsList(count));
}, _lifetime);
}
void Controller::exportPersonalInfo() {
_api.requestPersonalInfo([=](Data::PersonalInfo &&result) {
_writer->writePersonal(result);
if (ioCatchError(_writer->writePersonal(result))) {
return;
}
exportNext();
});
}
void Controller::exportUserpics() {
_api.requestUserpics([=](Data::UserpicsInfo &&start) {
_writer->writeUserpicsStart(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) {
_writer->writeUserpicsSlice(slice);
if (ioCatchError(_writer->writeUserpicsSlice(slice))) {
return false;
}
_userpicsWritten += slice.list.size();
setState(stateUserpics(DownloadProgress()));
return true;
}, [=] {
_writer->writeUserpicsEnd();
if (ioCatchError(_writer->writeUserpicsEnd())) {
return;
}
exportNext();
});
}
void Controller::exportContacts() {
_api.requestContacts([=](Data::ContactsList &&result) {
_writer->writeContactsList(result);
if (ioCatchError(_writer->writeContactsList(result))) {
return;
}
exportNext();
});
}
void Controller::exportSessions() {
_api.requestSessions([=](Data::SessionsList &&result) {
_writer->writeSessionsList(result);
if (ioCatchError(_writer->writeSessionsList(result))) {
return;
}
exportNext();
});
}
void Controller::exportDialogs() {
_writer->writeDialogsStart(_dialogsInfo);
if (ioCatchError(_writer->writeDialogsStart(_dialogsInfo))) {
return;
}
exportNextDialog();
}
@ -379,22 +432,42 @@ void Controller::exportNextDialog() {
const auto index = ++_dialogIndex;
if (index < _dialogsInfo.list.size()) {
const auto &info = _dialogsInfo.list[index];
_writer->writeDialogStart(info);
_api.requestMessages(info, [=](Data::MessagesSlice &&result) {
_writer->writeDialogSlice(result);
_api.requestMessages(info, [=](const Data::DialogInfo &info) {
if (ioCatchError(_writer->writeDialogStart(info))) {
return false;
}
_messagesWritten = 0;
_messagesCount = info.messagesCount;
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;
}, [=] {
_writer->writeDialogEnd();
if (ioCatchError(_writer->writeDialogEnd())) {
return;
}
exportNextDialog();
});
return;
}
_writer->writeDialogsEnd();
if (ioCatchError(_writer->writeDialogsEnd())) {
return;
}
exportNext();
}
void Controller::exportLeftChannels() {
_writer->writeLeftChannelsStart(_leftChannelsInfo);
if (ioCatchError(_writer->writeLeftChannelsStart(_leftChannelsInfo))) {
return;
}
exportNextLeftChannel();
}
@ -402,18 +475,36 @@ void Controller::exportLeftChannels() {
void Controller::exportNextLeftChannel() {
const auto index = ++_leftChannelIndex;
if (index < _leftChannelsInfo.list.size()) {
const auto &chat = _leftChannelsInfo.list[index];
_writer->writeLeftChannelStart(chat);
_api.requestMessages(chat, [=](Data::MessagesSlice &&result) {
_writer->writeLeftChannelSlice(result);
const auto &info = _leftChannelsInfo.list[index];
_api.requestMessages(info, [=](const Data::DialogInfo &info) {
if (ioCatchError(_writer->writeLeftChannelStart(info))) {
return false;
}
_messagesWritten = 0;
_messagesCount = info.messagesCount;
setState(stateLeftChannels(DownloadProgress()));
return true;
}, [=](DownloadProgress progress) {
setState(stateLeftChannels(progress));
return true;
}, [=](Data::MessagesSlice &&result) {
if (ioCatchError(_writer->writeLeftChannelSlice(result))) {
return false;
}
_messagesWritten += result.list.size();
setState(stateLeftChannels(DownloadProgress()));
return true;
}, [=] {
_writer->writeLeftChannelEnd();
if (ioCatchError(_writer->writeLeftChannelEnd())) {
return;
}
exportNextLeftChannel();
});
return;
}
_writer->writeLeftChannelsEnd();
if (ioCatchError(_writer->writeLeftChannelsEnd())) {
return;
}
exportNext();
}
@ -451,9 +542,12 @@ ProcessingState Controller::statePersonalInfo() const {
return prepareState(Step::PersonalInfo);
}
ProcessingState Controller::stateUserpics() const {
ProcessingState Controller::stateUserpics(DownloadProgress progress) const {
return prepareState(Step::Userpics, [&](ProcessingState &result) {
result.entityIndex = _userpicsWritten + progress.itemIndex;
result.entityCount = std::max(_userpicsCount, result.entityIndex);
result.bytesLoaded = progress.ready;
result.bytesCount = progress.total;
});
}
@ -465,26 +559,36 @@ ProcessingState Controller::stateSessions() const {
return prepareState(Step::Sessions);
}
ProcessingState Controller::stateLeftChannels() const {
ProcessingState Controller::stateLeftChannels(
DownloadProgress progress) const {
const auto step = Step::LeftChannels;
return prepareState(step, [&](ProcessingState &result) {
//result.entityIndex = processed;
//result.entityCount = std::max(processed, substepsInStep(step));
result.entityIndex = _leftChannelIndex;
result.entityCount = _leftChannelsInfo.list.size();
result.itemIndex = _messagesWritten + progress.itemIndex;
result.itemCount = std::max(_messagesCount, result.entityIndex);
result.bytesLoaded = progress.ready;
result.bytesCount = progress.total;
});
}
ProcessingState Controller::stateDialogs() const {
ProcessingState Controller::stateDialogs(DownloadProgress progress) const {
const auto step = Step::Dialogs;
return prepareState(step, [&](ProcessingState &result) {
//result.entityIndex = processed;
//result.entityCount = std::max(processed, substepsInStep(step));
result.entityIndex = _dialogIndex;
result.entityCount = _dialogsInfo.list.size();
result.itemIndex = _messagesWritten + progress.itemIndex;
result.itemCount = std::max(_messagesCount, result.entityIndex);
result.bytesLoaded = progress.ready;
result.bytesCount = progress.total;
});
}
int Controller::substepsInStep(Step step) const {
Expects(_substepsInStep.size() > static_cast<int>(step));
Expects(_substepsInStep != 0);
Expects(_substepsInStep->size() > static_cast<int>(step));
return _substepsInStep[static_cast<int>(step)];
return (*_substepsInStep)[static_cast<int>(step)];
}
void Controller::setFinishedState() {

View File

@ -50,7 +50,7 @@ struct ProcessingState {
Step step = Step::Initializing;
std::vector<int> substepsInStep;
std::shared_ptr<const std::vector<int>> substepsInStep;
int entityIndex = 0;
int entityCount = 1;
@ -68,15 +68,13 @@ struct ProcessingState {
};
struct ErrorState {
enum class Type {
Unknown,
API,
IO,
};
Type type = Type::Unknown;
base::optional<RPCError> apiError;
base::optional<QString> ioErrorPath;
struct ApiErrorState {
RPCError data;
};
struct OutputErrorState {
QString path;
};
@ -88,7 +86,8 @@ struct FinishedState {
using State = base::optional_variant<
PasswordCheckState,
ProcessingState,
ErrorState,
ApiErrorState,
OutputErrorState,
FinishedState>;
//struct PasswordUpdate {

View File

@ -25,41 +25,55 @@ struct Settings;
namespace Output {
struct Result;
enum class Format {
Text,
Yaml,
Html,
Json,
};
class AbstractWriter {
public:
virtual bool start(const Settings &settings) = 0;
[[nodiscard]] virtual Result start(const Settings &settings) = 0;
virtual bool writePersonal(const Data::PersonalInfo &data) = 0;
[[nodiscard]] virtual Result writePersonal(
const Data::PersonalInfo &data) = 0;
virtual bool writeUserpicsStart(const Data::UserpicsInfo &data) = 0;
virtual bool writeUserpicsSlice(const Data::UserpicsSlice &data) = 0;
virtual bool writeUserpicsEnd() = 0;
[[nodiscard]] virtual Result writeUserpicsStart(
const Data::UserpicsInfo &data) = 0;
[[nodiscard]] virtual Result writeUserpicsSlice(
const Data::UserpicsSlice &data) = 0;
[[nodiscard]] virtual Result writeUserpicsEnd() = 0;
virtual bool writeContactsList(const Data::ContactsList &data) = 0;
[[nodiscard]] virtual Result writeContactsList(
const Data::ContactsList &data) = 0;
virtual bool writeSessionsList(const Data::SessionsList &data) = 0;
[[nodiscard]] virtual Result writeSessionsList(
const Data::SessionsList &data) = 0;
virtual bool writeDialogsStart(const Data::DialogsInfo &data) = 0;
virtual bool writeDialogStart(const Data::DialogInfo &data) = 0;
virtual bool writeDialogSlice(const Data::MessagesSlice &data) = 0;
virtual bool writeDialogEnd() = 0;
virtual bool writeDialogsEnd() = 0;
[[nodiscard]] virtual Result writeDialogsStart(
const Data::DialogsInfo &data) = 0;
[[nodiscard]] virtual Result writeDialogStart(
const Data::DialogInfo &data) = 0;
[[nodiscard]] virtual Result writeDialogSlice(
const Data::MessagesSlice &data) = 0;
[[nodiscard]] virtual Result writeDialogEnd() = 0;
[[nodiscard]] virtual Result writeDialogsEnd() = 0;
virtual bool writeLeftChannelsStart(const Data::DialogsInfo &data) = 0;
virtual bool writeLeftChannelStart(const Data::DialogInfo &data) = 0;
virtual bool writeLeftChannelSlice(const Data::MessagesSlice &data) = 0;
virtual bool writeLeftChannelEnd() = 0;
virtual bool writeLeftChannelsEnd() = 0;
[[nodiscard]] virtual Result writeLeftChannelsStart(
const Data::DialogsInfo &data) = 0;
[[nodiscard]] virtual Result writeLeftChannelStart(
const Data::DialogInfo &data) = 0;
[[nodiscard]] virtual Result writeLeftChannelSlice(
const Data::MessagesSlice &data) = 0;
[[nodiscard]] virtual Result writeLeftChannelEnd() = 0;
[[nodiscard]] virtual Result writeLeftChannelsEnd() = 0;
virtual bool finish() = 0;
[[nodiscard]] virtual Result finish() = 0;
virtual QString mainFilePath() = 0;
[[nodiscard]] virtual QString mainFilePath() = 0;
virtual ~AbstractWriter() = default;

View File

@ -7,6 +7,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "export/output/export_output_file.h"
#include "export/output/export_output_result.h"
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
@ -26,47 +28,57 @@ bool File::empty() const {
return !_offset;
}
File::Result File::writeBlock(const QByteArray &block) {
Result File::writeBlock(const QByteArray &block) {
const auto result = writeBlockAttempt(block);
if (result != Result::Success) {
if (!result) {
_file.clear();
}
return result;
}
File::Result File::writeBlockAttempt(const QByteArray &block) {
if (const auto result = reopen(); result != Result::Success) {
Result File::writeBlockAttempt(const QByteArray &block) {
if (const auto result = reopen(); !result) {
return result;
}
return (_file->write(block) == block.size() && _file->flush())
? Result::Success
: Result::Error;
if (_file->write(block) == block.size() && _file->flush()) {
_offset += block.size();
return Result::Success();
}
return error();
}
File::Result File::reopen() {
Result File::reopen() {
if (_file && _file->isOpen()) {
return Result::Success;
return Result::Success();
}
_file.emplace(_path);
if (_file->exists()) {
if (_file->size() < _offset) {
return Result::FatalError;
return fatalError();
} else if (!_file->resize(_offset)) {
return Result::Error;
return error();
}
} else if (_offset > 0) {
return Result::FatalError;
return fatalError();
}
if (_file->open(QIODevice::Append)) {
return Result::Success;
return Result::Success();
}
const auto info = QFileInfo(_path);
const auto dir = info.absoluteDir();
return (!dir.exists()
&& dir.mkpath(dir.absolutePath())
&& _file->open(QIODevice::Append))
? Result::Success
: Result::Error;
? Result::Success()
: error();
}
Result File::error() const {
return Result(Result::Type::Error, _path);
}
Result File::fatalError() const {
return Result(Result::Type::FatalError, _path);
}
QString File::PrepareRelativePath(

View File

@ -16,27 +16,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Export {
namespace Output {
struct Result;
class File {
public:
File(const QString &path);
int size() const;
bool empty() const;
[[nodiscard]] int size() const;
[[nodiscard]] bool empty() const;
enum class Result {
Success,
Error,
FatalError,
};
Result writeBlock(const QByteArray &block);
[[nodiscard]] Result writeBlock(const QByteArray &block);
static QString PrepareRelativePath(
[[nodiscard]] static QString PrepareRelativePath(
const QString &folder,
const QString &suggested);
private:
Result reopen();
Result writeBlockAttempt(const QByteArray &block);
[[nodiscard]] Result reopen();
[[nodiscard]] Result writeBlockAttempt(const QByteArray &block);
[[nodiscard]] Result error() const;
[[nodiscard]] Result fatalError() const;
QString _path;
int _offset = 0;

View File

@ -0,0 +1,48 @@
/*
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
*/
#pragma once
#include <QtCore/QString>
namespace Export {
namespace Output {
struct Result {
enum class Type : char {
Success,
Error,
FatalError
};
Result(Type type, QString path) : type(type), path(path) {
}
static Result Success() {
return Result(Type::Success, QString());
}
bool isSuccess() const {
return type == Type::Success;
}
bool isError() const {
return (type == Type::Error) || (type == Type::FatalError);
}
bool isFatalError() const {
return (type == Type::FatalError);
}
explicit operator bool() const {
return isSuccess();
}
QString path;
Type type;
};
} // namespace Output
} // namespace Export

View File

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "export/output/export_output_text.h"
#include "export/output/export_output_result.h"
#include "export/data/export_data_types.h"
#include "core/utils.h"
@ -431,16 +432,16 @@ QByteArray SerializeMessage(
} // namespace
bool TextWriter::start(const Settings &settings) {
Result TextWriter::start(const Settings &settings) {
Expects(settings.path.endsWith('/'));
_settings = base::duplicate(settings);
_result = fileWithRelativePath(mainFileRelativePath());
return true;
_summary = fileWithRelativePath(mainFileRelativePath());
return Result::Success();
}
bool TextWriter::writePersonal(const Data::PersonalInfo &data) {
Expects(_result != nullptr);
Result TextWriter::writePersonal(const Data::PersonalInfo &data) {
Expects(_summary != nullptr);
const auto &info = data.user.info;
const auto serialized = "Personal information"
@ -454,25 +455,25 @@ bool TextWriter::writePersonal(const Data::PersonalInfo &data) {
{ "Bio", data.bio },
})
+ kLineBreak;
return _result->writeBlock(serialized) == File::Result::Success;
return _summary->writeBlock(serialized);
}
bool TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
Expects(_result != nullptr);
Result TextWriter::writeUserpicsStart(const Data::UserpicsInfo &data) {
Expects(_summary != nullptr);
_userpicsCount = data.count;
if (!_userpicsCount) {
return true;
return Result::Success();
}
const auto serialized = "Personal photos "
"(" + Data::NumberToString(_userpicsCount) + ")"
+ kLineBreak
+ kLineBreak;
return _result->writeBlock(serialized) == File::Result::Success;
return _summary->writeBlock(serialized);
}
bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
Expects(_result != nullptr);
Result TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
Expects(_summary != nullptr);
Expects(!data.list.empty());
auto lines = QByteArray();
@ -489,22 +490,22 @@ bool TextWriter::writeUserpicsSlice(const Data::UserpicsSlice &data) {
}
lines.append(kLineBreak);
}
return _result->writeBlock(lines) == File::Result::Success;
return _summary->writeBlock(lines);
}
bool TextWriter::writeUserpicsEnd() {
Expects(_result != nullptr);
Result TextWriter::writeUserpicsEnd() {
Expects(_summary != nullptr);
return (_userpicsCount > 0)
? _result->writeBlock(kLineBreak) == File::Result::Success
: true;
? _summary->writeBlock(kLineBreak)
: Result::Success();
}
bool TextWriter::writeContactsList(const Data::ContactsList &data) {
Expects(_result != nullptr);
Result TextWriter::writeContactsList(const Data::ContactsList &data) {
Expects(_summary != nullptr);
if (data.list.empty()) {
return true;
return Result::Success();
}
const auto file = fileWithRelativePath("contacts.txt");
@ -529,22 +530,22 @@ bool TextWriter::writeContactsList(const Data::ContactsList &data) {
}
}
const auto full = JoinList(kLineBreak, list);
if (file->writeBlock(full) != File::Result::Success) {
return false;
if (const auto result = file->writeBlock(full); !result) {
return result;
}
const auto header = "Contacts "
"(" + Data::NumberToString(data.list.size()) + ") - contacts.txt"
+ kLineBreak
+ kLineBreak;
return _result->writeBlock(header) == File::Result::Success;
return _summary->writeBlock(header);
}
bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
Expects(_result != nullptr);
Result TextWriter::writeSessionsList(const Data::SessionsList &data) {
Expects(_summary != nullptr);
if (data.list.empty()) {
return true;
return Result::Success();
}
const auto file = fileWithRelativePath("sessions.txt");
@ -570,67 +571,68 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
}));
}
const auto full = JoinList(kLineBreak, list);
if (file->writeBlock(full) != File::Result::Success) {
return false;
if (const auto result = file->writeBlock(full); !result) {
return result;
}
const auto header = "Sessions "
"(" + Data::NumberToString(data.list.size()) + ") - sessions.txt"
+ kLineBreak
+ kLineBreak;
return _result->writeBlock(header) == File::Result::Success;
return _summary->writeBlock(header);
}
bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
Result TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
return writeChatsStart(data, "Chats", "chats.txt");
}
bool TextWriter::writeDialogStart(const Data::DialogInfo &data) {
Result TextWriter::writeDialogStart(const Data::DialogInfo &data) {
return writeChatStart(data);
}
bool TextWriter::writeDialogSlice(const Data::MessagesSlice &data) {
Result TextWriter::writeDialogSlice(const Data::MessagesSlice &data) {
return writeChatSlice(data);
}
bool TextWriter::writeDialogEnd() {
Result TextWriter::writeDialogEnd() {
return writeChatEnd();
}
bool TextWriter::writeDialogsEnd() {
return true;
Result TextWriter::writeDialogsEnd() {
return Result::Success();
}
bool TextWriter::writeLeftChannelsStart(const Data::DialogsInfo &data) {
Result TextWriter::writeLeftChannelsStart(const Data::DialogsInfo &data) {
return writeChatsStart(data, "Left chats", "left_chats.txt");
}
bool TextWriter::writeLeftChannelStart(const Data::DialogInfo &data) {
Result TextWriter::writeLeftChannelStart(const Data::DialogInfo &data) {
return writeChatStart(data);
}
bool TextWriter::writeLeftChannelSlice(const Data::MessagesSlice &data) {
Result TextWriter::writeLeftChannelSlice(const Data::MessagesSlice &data) {
return writeChatSlice(data);
}
bool TextWriter::writeLeftChannelEnd() {
Result TextWriter::writeLeftChannelEnd() {
return writeChatEnd();
}
bool TextWriter::writeLeftChannelsEnd() {
return true;
Result TextWriter::writeLeftChannelsEnd() {
return Result::Success();
}
bool TextWriter::writeChatsStart(
Result TextWriter::writeChatsStart(
const Data::DialogsInfo &data,
const QByteArray &listName,
const QString &fileName) {
Expects(_result != nullptr);
Expects(_summary != nullptr);
if (data.list.empty()) {
return true;
return Result::Success();
}
_dialogIndex = 0;
_dialogsCount = data.list.size();
using Type = Data::DialogInfo::Type;
@ -676,8 +678,8 @@ bool TextWriter::writeChatsStart(
}));
}
const auto full = JoinList(kLineBreak, list);
if (file->writeBlock(full) != File::Result::Success) {
return false;
if (const auto result = file->writeBlock(full); !result) {
return result;
}
const auto header = listName + " "
@ -685,10 +687,10 @@ bool TextWriter::writeChatsStart(
+ fileName.toUtf8()
+ kLineBreak
+ kLineBreak;
return _result->writeBlock(header) == File::Result::Success;
return _summary->writeBlock(header);
}
bool TextWriter::writeChatStart(const Data::DialogInfo &data) {
Result TextWriter::writeChatStart(const Data::DialogInfo &data) {
Expects(_chat == nullptr);
Expects(_dialogIndex < _dialogsCount);
@ -697,10 +699,10 @@ bool TextWriter::writeChatStart(const Data::DialogInfo &data) {
_chat = fileWithRelativePath(data.relativePath + "messages.txt");
_dialogEmpty = true;
_dialogOnlyMy = data.onlyMyMessages;
return true;
return Result::Success();
}
bool TextWriter::writeChatSlice(const Data::MessagesSlice &data) {
Result TextWriter::writeChatSlice(const Data::MessagesSlice &data) {
Expects(_chat != nullptr);
Expects(!data.list.empty());
@ -717,23 +719,26 @@ bool TextWriter::writeChatSlice(const Data::MessagesSlice &data) {
const auto full = _chat->empty()
? JoinList(kLineBreak, list)
: kLineBreak + JoinList(kLineBreak, list);
return _chat->writeBlock(full) == File::Result::Success;
return _chat->writeBlock(full);
}
bool TextWriter::writeChatEnd() {
Result TextWriter::writeChatEnd() {
Expects(_chat != nullptr);
if (_dialogEmpty) {
_chat->writeBlock(_dialogOnlyMy
const auto result = _chat->writeBlock(_dialogOnlyMy
? "No outgoing messages in this chat."
: "No messages in this chat.");
if (!result) {
return result;
}
}
_chat = nullptr;
return true;
return Result::Success();
}
bool TextWriter::finish() {
return true;
Result TextWriter::finish() {
return Result::Success();
}
QString TextWriter::mainFilePath() {

View File

@ -16,31 +16,31 @@ namespace Output {
class TextWriter : public AbstractWriter {
public:
bool start(const Settings &settings) override;
Result start(const Settings &settings) override;
bool writePersonal(const Data::PersonalInfo &data) override;
Result writePersonal(const Data::PersonalInfo &data) override;
bool writeUserpicsStart(const Data::UserpicsInfo &data) override;
bool writeUserpicsSlice(const Data::UserpicsSlice &data) override;
bool writeUserpicsEnd() override;
Result writeUserpicsStart(const Data::UserpicsInfo &data) override;
Result writeUserpicsSlice(const Data::UserpicsSlice &data) override;
Result writeUserpicsEnd() override;
bool writeContactsList(const Data::ContactsList &data) override;
Result writeContactsList(const Data::ContactsList &data) override;
bool writeSessionsList(const Data::SessionsList &data) override;
Result writeSessionsList(const Data::SessionsList &data) override;
bool writeDialogsStart(const Data::DialogsInfo &data) override;
bool writeDialogStart(const Data::DialogInfo &data) override;
bool writeDialogSlice(const Data::MessagesSlice &data) override;
bool writeDialogEnd() override;
bool writeDialogsEnd() override;
Result writeDialogsStart(const Data::DialogsInfo &data) override;
Result writeDialogStart(const Data::DialogInfo &data) override;
Result writeDialogSlice(const Data::MessagesSlice &data) override;
Result writeDialogEnd() override;
Result writeDialogsEnd() override;
bool writeLeftChannelsStart(const Data::DialogsInfo &data) override;
bool writeLeftChannelStart(const Data::DialogInfo &data) override;
bool writeLeftChannelSlice(const Data::MessagesSlice &data) override;
bool writeLeftChannelEnd() override;
bool writeLeftChannelsEnd() override;
Result writeLeftChannelsStart(const Data::DialogsInfo &data) override;
Result writeLeftChannelStart(const Data::DialogInfo &data) override;
Result writeLeftChannelSlice(const Data::MessagesSlice &data) override;
Result writeLeftChannelEnd() override;
Result writeLeftChannelsEnd() override;
bool finish() override;
Result finish() override;
QString mainFilePath() override;
@ -49,17 +49,17 @@ private:
QString pathWithRelativePath(const QString &path) const;
std::unique_ptr<File> fileWithRelativePath(const QString &path) const;
bool writeChatsStart(
Result writeChatsStart(
const Data::DialogsInfo &data,
const QByteArray &listName,
const QString &fileName);
bool writeChatStart(const Data::DialogInfo &data);
bool writeChatSlice(const Data::MessagesSlice &data);
bool writeChatEnd();
Result writeChatStart(const Data::DialogInfo &data);
Result writeChatSlice(const Data::MessagesSlice &data);
Result writeChatEnd();
Settings _settings;
std::unique_ptr<File> _result;
std::unique_ptr<File> _summary;
int _userpicsCount = 0;
int _dialogsCount = 0;
@ -67,10 +67,6 @@ private:
bool _dialogOnlyMy = false;
bool _dialogEmpty = true;
int _leftChannelsCount = 0;
int _leftChannelIndex = 0;
bool _leftChannelEmpty = true;
std::unique_ptr<File> _chat;
};

View File

@ -37,3 +37,8 @@ exportFileSizeLabel: LabelSimple(defaultLabelSimple) {
}
exportFileSizePadding: margins(22px, 8px, 22px, 8px);
exportFileSizeLabelBottom: 18px;
exportErrorLabel: FlatLabel(boxLabel) {
minWidth: 175px;
align: align(top);
textFg: boxTextFgError;
}

View File

@ -0,0 +1,53 @@
/*
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_content.h"
#include "lang/lang_keys.h"
namespace Export {
namespace View {
Content ContentFromState(const ProcessingState &state) {
using Step = ProcessingState::Step;
auto result = Content();
const auto push = [&](
const QString &id,
const QString &label,
const QString &info,
float64 progress) {
result.rows.push_back({ id, label, info, progress });
};
switch (state.step) {
case Step::Initializing:
case Step::LeftChannelsList:
case Step::DialogsList:
case Step::PersonalInfo:
case Step::Userpics:
case Step::Contacts:
case Step::Sessions:
case Step::LeftChannels:
case Step::Dialogs:
push("init", lang(lng_export_state_initializing), QString(), 0.);
if (state.entityCount > 0) {
push("entity", QString(), QString::number(state.entityIndex) + '/' + QString::number(state.entityCount), 0.);
}
if (state.itemCount > 0) {
push("item", QString(), QString::number(state.itemIndex) + '/' + QString::number(state.itemCount), 0.);
}
if (state.bytesCount > 0) {
push("bytes", QString(), QString::number(state.bytesLoaded) + '/' + QString::number(state.bytesCount), 0.);
}
break;
default: Unexpected("Step in ContentFromState.");
}
return result;
}
} // namespace View
} // namespace Export

View File

@ -0,0 +1,41 @@
/*
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
*/
#pragma once
#include "export/export_controller.h"
namespace Export {
namespace View {
struct Content {
struct Row {
QString id;
QString label;
QString info;
float64 progress = 0.;
};
std::vector<Row> rows;
};
Content ContentFromState(const ProcessingState &state);
inline auto ContentFromState(rpl::producer<State> state) {
return std::move(
state
) | rpl::filter([](const State &state) {
return state.template is<ProcessingState>();
}) | rpl::map([](const State &state) {
return ContentFromState(
state.template get_unchecked<ProcessingState>());
});
}
} // namespace View
} // namespace Export

View File

@ -8,8 +8,11 @@ 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/view/export_view_done.h"
#include "ui/widgets/labels.h"
#include "ui/widgets/separate_panel.h"
#include "ui/wrap/padding_wrap.h"
#include "lang/lang_keys.h"
#include "core/file_utilities.h"
#include "styles/style_export.h"
@ -43,6 +46,9 @@ void PanelController::showSettings() {
settings->startClicks(
) | rpl::start_with_next([=](const Settings &settings) {
_panel->showInner(base::make_unique_q<ProgressWidget>(
_panel.get(),
ContentFromState(_process->state())));
_process->startExport(settings);
}, settings->lifetime());
@ -54,6 +60,34 @@ void PanelController::showSettings() {
_panel->showInner(std::move(settings));
}
void PanelController::showError(const ApiErrorState &error) {
showError("API Error happened :(\n"
+ QString::number(error.data.code()) + ": " + error.data.type()
+ "\n" + error.data.description());
}
void PanelController::showError(const OutputErrorState &error) {
showError("Disk Error happened :(\n"
"Could not write path:\n" + error.path);
}
void PanelController::showError(const QString &text) {
auto container = base::make_unique_q<Ui::PaddingWrap<Ui::FlatLabel>>(
_panel.get(),
object_ptr<Ui::FlatLabel>(
_panel.get(),
text,
Ui::FlatLabel::InitType::Simple,
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));
}
rpl::producer<> PanelController::closed() const {
return _panelCloseEvents.events(
) | rpl::flatten_latest(
@ -67,7 +101,11 @@ void PanelController::updateState(State &&state) {
createPanel();
}
_state = std::move(state);
if (const auto finished = base::get_if<FinishedState>(&_state)) {
if (const auto apiError = base::get_if<ApiErrorState>(&_state)) {
showError(*apiError);
} else if (const auto error = base::get_if<OutputErrorState>(&_state)) {
showError(*error);
} else if (const auto finished = base::get_if<FinishedState>(&_state)) {
const auto path = finished->path;
auto done = base::make_unique_q<DoneWidget>(_panel.get());

View File

@ -31,6 +31,9 @@ private:
void createPanel();
void updateState(State &&state);
void showSettings();
void showError(const ApiErrorState &error);
void showError(const OutputErrorState &error);
void showError(const QString &text);
not_null<ControllerWrap*> _process;

View File

@ -0,0 +1,46 @@
/*
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_progress.h"
#include "ui/widgets/labels.h"
#include "styles/style_boxes.h"
namespace Export {
namespace View {
ProgressWidget::ProgressWidget(
QWidget *parent,
rpl::producer<Content> content)
: RpWidget(parent) {
const auto label = Ui::CreateChild<Ui::FlatLabel>(this, st::boxLabel);
sizeValue(
) | rpl::start_with_next([=](QSize size) {
label->setGeometry(QRect(QPoint(), size));
}, label->lifetime());
std::move(
content
) | rpl::start_with_next([=](Content &&content) {
auto text = QString();
for (const auto &row : content.rows) {
text += row.id + ' ' + row.info + ' ' + row.label + '\n';
}
label->setText(text);
updateState(std::move(content));
}, lifetime());
}
void ProgressWidget::updateState(Content &&content) {
_content = std::move(content);
}
ProgressWidget::~ProgressWidget() = default;
} // namespace View
} // namespace Export

View File

@ -0,0 +1,35 @@
/*
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
*/
#pragma once
#include "ui/rp_widget.h"
#include "export/view/export_view_content.h"
namespace Export {
namespace View {
class ProgressWidget : public Ui::RpWidget {
public:
ProgressWidget(
QWidget *parent,
rpl::producer<Content> content);
~ProgressWidget();
private:
void updateState(Content &&content);
Content _content;
class Row;
std::vector<base::unique_qptr<Row>> _rows;
};
} // namespace View
} // namespace Export

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtp_instance.h"
#include "mtproto/rpc_sender.h"
#include "mtproto/session.h"
namespace MTP {
@ -178,7 +179,13 @@ void ConcurrentSender::senderRequestDone(
mtpRequestId requestId,
bytes::const_span result) {
if (auto handlers = _requests.take(requestId)) {
std::move(handlers->done)(requestId, result);
try {
std::move(handlers->done)(requestId, result);
} catch (Exception &e) {
std::move(handlers->fail)(requestId, internal::rpcClientError(
"RESPONSE_PARSE_FAILED",
QString("exception text: ") + e.what()));
}
}
}

View File

@ -203,14 +203,11 @@ void ConcurrentSender::RequestBuilder::setDoneHandler(
_handlers.done = [handler = std::move(invoke)](
mtpRequestId requestId,
bytes::const_span result) mutable {
try {
auto from = reinterpret_cast<const mtpPrime*>(result.data());
const auto end = from + result.size() / sizeof(mtpPrime);
Response data;
data.read(from, end);
std::move(handler)(requestId, std::move(data));
} catch (...) {
}
auto from = reinterpret_cast<const mtpPrime*>(result.data());
const auto end = from + result.size() / sizeof(mtpPrime);
Response data;
data.read(from, end);
std::move(handler)(requestId, std::move(data));
};
}

View File

@ -225,10 +225,14 @@
<(src_loc)/dialogs/dialogs_search_from_controllers.h
<(src_loc)/dialogs/dialogs_widget.cpp
<(src_loc)/dialogs/dialogs_widget.h
<(src_loc)/export/view/export_view_content.cpp
<(src_loc)/export/view/export_view_content.h
<(src_loc)/export/view/export_view_done.cpp
<(src_loc)/export/view/export_view_done.h
<(src_loc)/export/view/export_view_panel_controller.cpp
<(src_loc)/export/view/export_view_panel_controller.h
<(src_loc)/export/view/export_view_progress.cpp
<(src_loc)/export/view/export_view_progress.h
<(src_loc)/export/view/export_view_settings.cpp
<(src_loc)/export/view/export_view_settings.h
<(src_loc)/history/admin_log/history_admin_log_filter.cpp