From 7d4e23448eefbe5d52621b577e59a5c75d319845 Mon Sep 17 00:00:00 2001
From: John Preston <johnprestonmail@gmail.com>
Date: Mon, 18 Jun 2018 22:52:13 +0100
Subject: [PATCH] Better steps division in export.

---
 Telegram/Resources/scheme.tl                  |   2 +-
 .../export/data/export_data_types.cpp         |  52 +-
 .../export/data/export_data_types.h           |   8 +-
 .../SourceFiles/export/export_api_wrap.cpp    | 507 +++++++++++-------
 Telegram/SourceFiles/export/export_api_wrap.h |  50 +-
 .../SourceFiles/export/export_controller.cpp  | 408 ++++++++++----
 .../SourceFiles/export/export_controller.h    |  44 +-
 Telegram/SourceFiles/export/export_settings.h |  25 +-
 .../export/output/export_output_abstract.h    |   8 +-
 .../export/output/export_output_file.cpp      |   4 +
 .../export/output/export_output_file.h        |   1 +
 .../export/output/export_output_text.cpp      |  76 ++-
 .../export/output/export_output_text.h        |  23 +-
 13 files changed, 815 insertions(+), 393 deletions(-)

diff --git a/Telegram/Resources/scheme.tl b/Telegram/Resources/scheme.tl
index 9faad2bb7d..3f77507615 100644
--- a/Telegram/Resources/scheme.tl
+++ b/Telegram/Resources/scheme.tl
@@ -1266,7 +1266,7 @@ channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet =
 channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
 channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool;
 channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
-channels.getLeftChannels#90920196 = messages.Chats;
+channels.getLeftChannels#8341ecc0 offset:int = messages.Chats;
 
 bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
 bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
diff --git a/Telegram/SourceFiles/export/data/export_data_types.cpp b/Telegram/SourceFiles/export/data/export_data_types.cpp
index f957b69be8..4b8f504454 100644
--- a/Telegram/SourceFiles/export/data/export_data_types.cpp
+++ b/Telegram/SourceFiles/export/data/export_data_types.cpp
@@ -944,30 +944,23 @@ DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data) {
 	return result;
 }
 
-void InsertLeftDialog(
-		DialogsInfo &info,
-		const Chat &chat,
-		Message &&message) {
-	const auto projection = [](const DialogInfo &dialog) {
-		return std::make_tuple(
-			dialog.topMessageDate,
-			dialog.topMessageId,
-			BarePeerId(dialog.peerId));
-	};
-	const auto i = ranges::lower_bound(
-		info.list,
-		std::make_tuple(message.date, message.id, chat.id),
-		ranges::ordered_less{},
-		projection);
-
-	auto insert = DialogInfo();
-	insert.input = chat.input;
-	insert.name = chat.title;
-	insert.peerId = ChatPeerId(chat.id);
-	insert.topMessageDate = message.date;
-	insert.topMessageId = message.id;
-	insert.type = DialogTypeFromChat(chat);
-	info.list.insert(i, std::move(insert));
+DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data) {
+	auto result = DialogsInfo();
+	data.match([&](const auto &data) { //MTPDmessages_chats &data) {
+		result.list.reserve(data.vchats.v.size());
+		for (const auto &single : data.vchats.v) {
+			const auto chat = ParseChat(single);
+			auto info = DialogInfo();
+			info.input = chat.input;
+			info.name = chat.title;
+			info.peerId = ChatPeerId(chat.id);
+			info.topMessageDate = 0;
+			info.topMessageId = 0;
+			info.type = DialogTypeFromChat(chat);
+			result.list.push_back(std::move(info));
+		}
+	});
+	return result;
 }
 
 void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
@@ -995,6 +988,17 @@ void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings) {
 	}
 }
 
+void FinalizeLeftChannelsInfo(DialogsInfo &info, const Settings &settings) {
+	auto &list = info.list;
+	const auto digits = Data::NumberToString(list.size() - 1).size();
+	auto index = 0;
+	for (auto &dialog : list) {
+		const auto number = Data::NumberToString(++index, digits, '0');
+		dialog.relativePath = "Chats/left_" + number + '/';
+		dialog.onlyMyMessages = true;
+	}
+}
+
 MessagesSlice ParseMessagesSlice(
 		const MTPVector<MTPMessage> &data,
 		const MTPVector<MTPUser> &users,
diff --git a/Telegram/SourceFiles/export/data/export_data_types.h b/Telegram/SourceFiles/export/data/export_data_types.h
index f005a5ed26..93d85cf713 100644
--- a/Telegram/SourceFiles/export/data/export_data_types.h
+++ b/Telegram/SourceFiles/export/data/export_data_types.h
@@ -433,12 +433,12 @@ struct DialogsInfo {
 	std::vector<DialogInfo> list;
 };
 
+DialogInfo::Type DialogTypeFromChat(const Chat &chat);
+
 DialogsInfo ParseDialogsInfo(const MTPmessages_Dialogs &data);
-void InsertLeftDialog(
-	DialogsInfo &info,
-	const Chat &chat,
-	Message &&message);
+DialogsInfo ParseLeftChannelsInfo(const MTPmessages_Chats &data);
 void FinalizeDialogsInfo(DialogsInfo &info, const Settings &settings);
+void FinalizeLeftChannelsInfo(DialogsInfo &info, const Settings &settings);
 
 struct MessagesSlice {
 	std::vector<Message> list;
diff --git a/Telegram/SourceFiles/export/export_api_wrap.cpp b/Telegram/SourceFiles/export/export_api_wrap.cpp
index c80df6eb05..73234fe6b6 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.cpp
+++ b/Telegram/SourceFiles/export/export_api_wrap.cpp
@@ -63,6 +63,25 @@ LocationKey ComputeLocationKey(const Data::FileLocation &value) {
 	return result;
 }
 
+Settings::Type SettingsFromDialogsType(Data::DialogInfo::Type type) {
+	using DialogType = Data::DialogInfo::Type;
+	switch (type) {
+	case DialogType::Personal:
+		return Settings::Type::PersonalChats;
+	case DialogType::Bot:
+		return Settings::Type::BotChats;
+	case DialogType::PrivateGroup:
+		return Settings::Type::PrivateGroups;
+	case DialogType::PublicGroup:
+		return Settings::Type::PublicGroups;
+	case DialogType::PrivateChannel:
+		return Settings::Type::PrivateChannels;
+	case DialogType::PublicChannel:
+		return Settings::Type::PublicChannels;
+	}
+	return Settings::Type(0);
+}
+
 } // namespace
 
 class ApiWrap::LoadedFileCache {
@@ -81,6 +100,19 @@ private:
 
 };
 
+struct ApiWrap::StartProcess {
+	FnMut<void(StartInfo)> done;
+
+	enum class Step {
+		UserpicsCount,
+		DialogsCount,
+		LeftChannelsCount,
+	};
+	std::deque<Step> steps;
+	StartInfo info;
+
+};
+
 struct ApiWrap::UserpicsProcess {
 	FnMut<void(Data::UserpicsInfo&&)> start;
 	Fn<void(Data::UserpicsSlice&&)> handleSlice;
@@ -112,30 +144,36 @@ struct ApiWrap::FileProcess {
 
 };
 
-struct ApiWrap::DialogsProcess {
-	Data::DialogsInfo info;
-	std::map<int32, Data::Chat> left;
+struct ApiWrap::LeftChannelsProcess {
+	FnMut<void(Data::DialogsInfo&&)> done;
 
-	FnMut<void(const Data::DialogsInfo&)> start;
-	Fn<void(const Data::DialogInfo&)> startOne;
-	Fn<void(Data::MessagesSlice&&)> sliceOne;
-	Fn<void()> finishOne;
-	FnMut<void()> finish;
+	Data::DialogsInfo info;
+
+	rpl::variable<int> count;
+	int fullCount = 0;
+	bool finished = false;
+
+};
+
+struct ApiWrap::DialogsProcess {
+	FnMut<void(Data::DialogsInfo&&)> done;
+
+	Data::DialogsInfo info;
 
 	Data::TimeId offsetDate = 0;
 	int32 offsetId = 0;
 	MTPInputPeer offsetPeer = MTP_inputPeerEmpty();
 
-	struct Single;
-	std::unique_ptr<Single> single;
-	int singleIndex = -1;
+	rpl::variable<int> count;
 
 };
 
-struct ApiWrap::DialogsProcess::Single {
-	Single(const Data::DialogInfo &info);
-
+struct ApiWrap::ChatProcess {
 	Data::DialogInfo info;
+
+	Fn<void(Data::MessagesSlice&&)> handleSlice;
+	FnMut<void()> done;
+
 	int32 offsetId = 1;
 
 	base::optional<Data::MessagesSlice> slice;
@@ -173,10 +211,6 @@ base::optional<QString> ApiWrap::LoadedFileCache::find(
 ApiWrap::FileProcess::FileProcess(const QString &path) : file(path) {
 }
 
-ApiWrap::DialogsProcess::Single::Single(const Data::DialogInfo &info)
-: info(info) {
-}
-
 template <typename Request>
 auto ApiWrap::mainRequest(Request &&request) {
 	Expects(_takeoutId.has_value());
@@ -215,11 +249,158 @@ rpl::producer<RPCError> ApiWrap::errors() const {
 
 void ApiWrap::startExport(
 		const Settings &settings,
-		FnMut<void()> done) {
+		FnMut<void(StartInfo)> done) {
 	Expects(_settings == nullptr);
+	Expects(_startProcess == nullptr);
 
 	_settings = std::make_unique<Settings>(settings);
-	startMainSession(std::move(done));
+	_startProcess = std::make_unique<StartProcess>();
+	_startProcess->done = std::move(done);
+
+	using Step = StartProcess::Step;
+	if (_settings->types & Settings::Type::Userpics) {
+		_startProcess->steps.push_back(Step::UserpicsCount);
+	} else if (_settings->types & Settings::Type::AnyChatsMask) {
+		_startProcess->steps.push_back(Step::DialogsCount);
+	} else if (_settings->types & Settings::Type::GroupsChannelsMask) {
+		_startProcess->steps.push_back(Step::LeftChannelsCount);
+	}
+	startMainSession([=] {
+		sendNextStartRequest();
+	});
+}
+
+void ApiWrap::sendNextStartRequest() {
+	Expects(_startProcess != nullptr);
+
+	auto &steps = _startProcess->steps;
+	if (steps.empty()) {
+		finishStartProcess();
+		return;
+	}
+	const auto step = steps.front();
+	steps.pop_front();
+	switch (step) {
+	case StartProcess::Step::UserpicsCount:
+		return requestUserpicsCount();
+	case StartProcess::Step::DialogsCount:
+		return requestDialogsCount();
+	case StartProcess::Step::LeftChannelsCount:
+		return requestLeftChannelsCount();
+	}
+	Unexpected("Step in ApiWrap::sendNextStartRequest.");
+}
+
+void ApiWrap::requestUserpicsCount() {
+	Expects(_startProcess != nullptr);
+
+	mainRequest(MTPphotos_GetUserPhotos(
+		_user,
+		MTP_int(0),  // offset
+		MTP_long(0), // max_id
+		MTP_int(0)   // limit
+	)).done([=](const MTPphotos_Photos &result) {
+		Expects(_settings != nullptr);
+		Expects(_startProcess != nullptr);
+
+		_startProcess->info.userpicsCount = result.match(
+		[](const MTPDphotos_photos &data) {
+			return int(data.vphotos.v.size());
+		}, [](const MTPDphotos_photosSlice &data) {
+			return data.vcount.v;
+		});
+
+		sendNextStartRequest();
+	}).send();
+}
+
+void ApiWrap::requestDialogsCount() {
+	Expects(_startProcess != nullptr);
+
+	mainRequest(MTPmessages_GetDialogs(
+		MTP_flags(0),
+		MTP_int(0), // offset_date
+		MTP_int(0), // offset_id
+		MTP_inputPeerEmpty(), // offset_peer
+		MTP_int(1)
+	)).done([=](const MTPmessages_Dialogs &result) {
+		Expects(_settings != nullptr);
+		Expects(_startProcess != nullptr);
+
+		_startProcess->info.dialogsCount = result.match(
+		[](const MTPDmessages_dialogs &data) {
+			return int(data.vdialogs.v.size());
+		}, [](const MTPDmessages_dialogsSlice &data) {
+			return data.vcount.v;
+		});
+
+		sendNextStartRequest();
+	}).send();
+}
+
+void ApiWrap::requestLeftChannelsCount() {
+	Expects(_startProcess != nullptr);
+	Expects(_leftChannelsProcess == nullptr);
+
+	_leftChannelsProcess = std::make_unique<LeftChannelsProcess>();
+	requestLeftChannelsSliceGeneric([=] {
+		Expects(_startProcess != nullptr);
+		Expects(_leftChannelsProcess != nullptr);
+
+		_startProcess->info.leftChannelsCount
+			= _leftChannelsProcess->fullCount;
+		sendNextStartRequest();
+	});
+}
+
+void ApiWrap::finishStartProcess() {
+	Expects(_startProcess != nullptr);
+
+	const auto process = base::take(_startProcess);
+	process->done(process->info);
+}
+
+void ApiWrap::requestLeftChannelsList(
+		FnMut<void(Data::DialogsInfo&&)> done) {
+	Expects(_leftChannelsProcess != nullptr);
+
+	_leftChannelsProcess->done = std::move(done);
+	requestLeftChannelsSlice();
+}
+
+void ApiWrap::requestLeftChannelsSlice() {
+	requestLeftChannelsSliceGeneric([=] {
+		Expects(_leftChannelsProcess != nullptr);
+
+		if (_leftChannelsProcess->finished) {
+			const auto process = base::take(_leftChannelsProcess);
+			Data::FinalizeLeftChannelsInfo(process->info, *_settings);
+			process->done(std::move(process->info));
+		} else {
+			requestLeftChannelsSlice();
+		}
+	});
+}
+
+rpl::producer<int> ApiWrap::leftChannelsLoadedCount() const {
+	Expects(_leftChannelsProcess != nullptr);
+
+	return _leftChannelsProcess->count.value();
+}
+
+void ApiWrap::requestDialogsList(FnMut<void(Data::DialogsInfo&&)> done) {
+	Expects(_dialogsProcess == nullptr);
+
+	_dialogsProcess = std::make_unique<DialogsProcess>();
+	_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) {
@@ -287,8 +468,8 @@ void ApiWrap::requestUserpics(
 
 	mainRequest(MTPphotos_GetUserPhotos(
 		_user,
-		MTP_int(0),
-		MTP_long(0),
+		MTP_int(0),  // offset
+		MTP_long(0), // max_id
 		MTP_int(kUserpicsSliceLimit)
 	)).done([=](const MTPphotos_Photos &result) mutable {
 		Expects(_userpicsProcess != nullptr);
@@ -405,22 +586,18 @@ void ApiWrap::requestSessions(FnMut<void(Data::SessionsList&&)> done) {
 	}).send();
 }
 
-void ApiWrap::requestDialogs(
-		FnMut<void(const Data::DialogsInfo&)> start,
-		Fn<void(const Data::DialogInfo&)> startOne,
-		Fn<void(Data::MessagesSlice&&)> sliceOne,
-		Fn<void()> finishOne,
-		FnMut<void()> finish) {
-	Expects(_dialogsProcess == nullptr);
+void ApiWrap::requestMessages(
+		const Data::DialogInfo &info,
+		Fn<void(Data::MessagesSlice&&)> slice,
+		FnMut<void()> done) {
+	Expects(_chatProcess == nullptr);
 
-	_dialogsProcess = std::make_unique<DialogsProcess>();
-	_dialogsProcess->start = std::move(start);
-	_dialogsProcess->startOne = std::move(startOne);
-	_dialogsProcess->sliceOne = std::move(sliceOne);
-	_dialogsProcess->finishOne = std::move(finishOne);
-	_dialogsProcess->finish = std::move(finish);
+	_chatProcess = std::make_unique<ChatProcess>();
+	_chatProcess->info = info;
+	_chatProcess->handleSlice = std::move(slice);
+	_chatProcess->done = std::move(done);
 
-	requestDialogsSlice();
+	requestMessagesSlice();
 }
 
 void ApiWrap::requestDialogsSlice() {
@@ -432,7 +609,7 @@ void ApiWrap::requestDialogsSlice() {
 		MTP_int(_dialogsProcess->offsetId),
 		_dialogsProcess->offsetPeer,
 		MTP_int(kChatsSliceLimit)
-	)).done([=](const MTPmessages_Dialogs &result) mutable {
+	)).done([=](const MTPmessages_Dialogs &result) {
 		const auto finished = result.match(
 		[](const MTPDmessages_dialogs &data) {
 			return true;
@@ -442,7 +619,7 @@ void ApiWrap::requestDialogsSlice() {
 
 		auto info = Data::ParseDialogsInfo(result);
 		if (finished || info.list.empty()) {
-			requestLeftChannels();
+			finishDialogsList();
 		} else {
 			const auto &last = info.list.back();
 			_dialogsProcess->offsetId = last.topMessageId;
@@ -458,139 +635,86 @@ void ApiWrap::requestDialogsSlice() {
 
 void ApiWrap::appendDialogsSlice(Data::DialogsInfo &&info) {
 	Expects(_dialogsProcess != nullptr);
-	Expects(_settings != nullptr);
 
-	const auto types = _settings->types;
-	auto filtered = ranges::view::all(
-		info.list
-	) | ranges::view::filter([&](const Data::DialogInfo &info) {
-		const auto bit = [&] {
-			using DialogType = Data::DialogInfo::Type;
-			switch (info.type) {
-			case DialogType::Personal:
-				return Settings::Type::PersonalChats;
-			case DialogType::Bot:
-				return Settings::Type::BotChats;
-			case DialogType::PrivateGroup:
-				return Settings::Type::PrivateGroups;
-			case DialogType::PublicGroup:
-				return Settings::Type::PublicGroups;
-			case DialogType::PrivateChannel:
-				return Settings::Type::PrivateChannels;
-			case DialogType::PublicChannel:
-				return Settings::Type::PublicChannels;
-			}
-			return Settings::Type(0);
-		}();
-		return (types & bit) != 0;
-	});
-	auto &list = _dialogsProcess->info.list;
-	list.reserve(list.size());
-	for (auto &info : filtered) {
-		list.push_back(std::move(info));
-	}
+	appendChatsSlice(_dialogsProcess->info, std::move(info));
 }
 
-void ApiWrap::requestLeftChannels() {
+void ApiWrap::finishDialogsList() {
 	Expects(_dialogsProcess != nullptr);
 
-	mainRequest(MTPchannels_GetLeftChannels(
-	)).done([=](const MTPmessages_Chats &result) mutable {
-		Expects(_dialogsProcess != nullptr);
+	const auto process = base::take(_dialogsProcess);
 
-		const auto finished = result.match(
+	ranges::reverse(process->info.list);
+	Data::FinalizeDialogsInfo(process->info, *_settings);
+
+	process->done(std::move(process->info));
+}
+
+void ApiWrap::requestLeftChannelsSliceGeneric(FnMut<void()> done) {
+	Expects(_leftChannelsProcess != nullptr);
+
+	mainRequest(MTPchannels_GetLeftChannels(
+		MTP_int(_leftChannelsProcess->info.list.size())
+	)).done([=, done = std::move(done)](
+			const MTPmessages_Chats &result) mutable {
+		Expects(_leftChannelsProcess != nullptr);
+
+		appendLeftChannelsSlice(Data::ParseLeftChannelsInfo(result));
+
+		const auto process = _leftChannelsProcess.get();
+		process->fullCount = result.match(
+		[](const MTPDmessages_chats &data) {
+			return int(data.vchats.v.size());
+		}, [](const MTPDmessages_chatsSlice &data) {
+			return data.vcount.v;
+		});
+
+		process->finished = result.match(
 		[](const MTPDmessages_chats &data) {
 			return true;
 		}, [](const MTPDmessages_chatsSlice &data) {
 			return data.vchats.v.isEmpty();
 		});
 
-		_dialogsProcess->left = Data::ParseChatsList(*result.match(
-		[](const auto &data) {
-			return &data.vchats;
-		}));
-		requestLeftDialog();
+		process->count = process->info.list.size();
+
+		done();
 	}).send();
 }
 
-void ApiWrap::requestLeftDialog() {
-	Expects(_dialogsProcess != nullptr);
+void ApiWrap::appendLeftChannelsSlice(Data::DialogsInfo &&info) {
+	Expects(_leftChannelsProcess != nullptr);
 
-	auto &left = _dialogsProcess->left;
-	if (true || left.empty()) { // #TODO export
-		finishDialogsList();
-		return;
-	}
-	const auto key = std::move(*left.begin());
-	left.erase(key.first);
-
-	mainRequest(MTPmessages_Search(
-		MTP_flags(MTPmessages_Search::Flag::f_from_id),
-		key.second.input,
-		MTP_string(""), // query
-		_user,
-		MTP_inputMessagesFilterEmpty(),
-		MTP_int(0), // min_date
-		MTP_int(0), // max_date
-		MTP_int(0), // offset_id
-		MTP_int(0), // add_offset
-		MTP_int(1), // limit
-		MTP_int(0), // max_id
-		MTP_int(0), // min_id
-		MTP_int(0) // hash
-	)).done([=](const MTPmessages_Messages &result) {
-		Expects(_dialogsProcess != nullptr);
-
-		result.match([=](const MTPDmessages_messagesNotModified &data) {
-			error("Unexpected messagesNotModified received.");
-		}, [=](const auto &data) {
-			auto messages = Data::ParseMessagesList(
-				data.vmessages,
-				QString());
-			if (!messages.empty() && messages.begin()->second.date > 0) {
-				Data::InsertLeftDialog(
-					_dialogsProcess->info,
-					key.second,
-					std::move(messages.begin()->second));
-			}
-			requestLeftDialog();
-		});
-	}).send();
+	appendChatsSlice(_leftChannelsProcess->info, std::move(info));
 }
 
-void ApiWrap::finishDialogsList() {
-	Expects(_dialogsProcess != nullptr);
+void ApiWrap::appendChatsSlice(
+		Data::DialogsInfo &to,
+		Data::DialogsInfo &&info) {
+	Expects(_settings != nullptr);
 
-	ranges::reverse(_dialogsProcess->info.list);
-	Data::FinalizeDialogsInfo(_dialogsProcess->info, *_settings);
-
-	_dialogsProcess->start(_dialogsProcess->info);
-	requestNextDialog();
-}
-
-void ApiWrap::requestNextDialog() {
-	Expects(_dialogsProcess != nullptr);
-	Expects(_dialogsProcess->single == nullptr);
-
-	const auto index = ++_dialogsProcess->singleIndex;
-	if (index < _dialogsProcess->info.list.size()) {
-		const auto &one = _dialogsProcess->info.list[index];
-		_dialogsProcess->single = std::make_unique<DialogsProcess::Single>(one);
-		_dialogsProcess->startOne(one);
-		requestMessagesSlice();
-		return;
+	const auto types = _settings->types;
+	auto filtered = ranges::view::all(
+		info.list
+	) | ranges::view::filter([&](const Data::DialogInfo &info) {
+		return (types & SettingsFromDialogsType(info.type)) != 0;
+	});
+	auto &list = to.list;
+	if (list.empty()) {
+		list = filtered | ranges::to_vector;
+	} else {
+		list.reserve(list.size() + info.list.size());
+		for (auto &info : filtered) {
+			list.push_back(std::move(info));
+		}
 	}
-	finishDialogs();
 }
 
 void ApiWrap::requestMessagesSlice() {
-	Expects(_dialogsProcess != nullptr);
-	Expects(_dialogsProcess->single != nullptr);
-
-	const auto process = _dialogsProcess->single.get();
+	Expects(_chatProcess != nullptr);
 
 	// #TODO export
-	if (process->info.input.match([](const MTPDinputPeerUser &value) {
+	if (_chatProcess->info.input.match([](const MTPDinputPeerUser &value) {
 		return !value.vaccess_hash.v;
 	}, [](const auto &data) { return false; })) {
 		finishMessages();
@@ -598,33 +722,31 @@ void ApiWrap::requestMessagesSlice() {
 	}
 
 	auto handleResult = [=](const MTPmessages_Messages &result) mutable {
-		Expects(_dialogsProcess != nullptr);
-		Expects(_dialogsProcess->single != nullptr);
+		Expects(_chatProcess != nullptr);
 
-		const auto process = _dialogsProcess->single.get();
 		result.match([&](const MTPDmessages_messagesNotModified &data) {
 			error("Unexpected messagesNotModified received.");
 		}, [&](const auto &data) {
 			if constexpr (MTPDmessages_messages::Is<decltype(data)>()) {
-				process->lastSlice = true;
+				_chatProcess->lastSlice = true;
 			}
 			loadMessagesFiles(Data::ParseMessagesSlice(
 				data.vmessages,
 				data.vusers,
 				data.vchats,
-				process->info.relativePath));
+				_chatProcess->info.relativePath));
 		});
 	};
-	if (process->info.onlyMyMessages) {
+	if (_chatProcess->info.onlyMyMessages) {
 		mainRequest(MTPmessages_Search(
 			MTP_flags(MTPmessages_Search::Flag::f_from_id),
-			process->info.input,
+			_chatProcess->info.input,
 			MTP_string(""), // query
 			_user,
 			MTP_inputMessagesFilterEmpty(),
 			MTP_int(0), // min_date
 			MTP_int(0), // max_date
-			MTP_int(process->offsetId),
+			MTP_int(_chatProcess->offsetId),
 			MTP_int(-kMessagesSliceLimit),
 			MTP_int(kMessagesSliceLimit),
 			MTP_int(0), // max_id
@@ -633,8 +755,8 @@ void ApiWrap::requestMessagesSlice() {
 		)).done(std::move(handleResult)).send();
 	} else {
 		mainRequest(MTPmessages_GetHistory(
-			process->info.input,
-			MTP_int(process->offsetId),
+			_chatProcess->info.input,
+			MTP_int(_chatProcess->offsetId),
 			MTP_int(0), // offset_date
 			MTP_int(-kMessagesSliceLimit),
 			MTP_int(kMessagesSliceLimit),
@@ -646,29 +768,25 @@ void ApiWrap::requestMessagesSlice() {
 }
 
 void ApiWrap::loadMessagesFiles(Data::MessagesSlice &&slice) {
-	Expects(_dialogsProcess != nullptr);
-	Expects(_dialogsProcess->single != nullptr);
-	Expects(!_dialogsProcess->single->slice.has_value());
+	Expects(_chatProcess != nullptr);
+	Expects(!_chatProcess->slice.has_value());
 
-	const auto process = _dialogsProcess->single.get();
 	if (slice.list.empty()) {
-		process->lastSlice = true;
+		_chatProcess->lastSlice = true;
 	}
-	process->slice = std::move(slice);
-	process->fileIndex = -1;
+	_chatProcess->slice = std::move(slice);
+	_chatProcess->fileIndex = -1;
 
 	loadNextMessageFile();
 }
 
 void ApiWrap::loadNextMessageFile() {
-	Expects(_dialogsProcess != nullptr);
-	Expects(_dialogsProcess->single != nullptr);
-	Expects(_dialogsProcess->single->slice.has_value());
+	Expects(_chatProcess != nullptr);
+	Expects(_chatProcess->slice.has_value());
 
-	const auto process = _dialogsProcess->single.get();
-	auto &list = process->slice->list;
+	auto &list = _chatProcess->slice->list;
 	while (true) {
-		const auto index = ++process->fileIndex;
+		const auto index = ++_chatProcess->fileIndex;
 		if (index >= list.size()) {
 			break;
 		}
@@ -684,17 +802,15 @@ void ApiWrap::loadNextMessageFile() {
 }
 
 void ApiWrap::finishMessagesSlice() {
-	Expects(_dialogsProcess != nullptr);
-	Expects(_dialogsProcess->single != nullptr);
-	Expects(_dialogsProcess->single->slice.has_value());
+	Expects(_chatProcess != nullptr);
+	Expects(_chatProcess->slice.has_value());
 
-	const auto process = _dialogsProcess->single.get();
-	auto slice = *base::take(process->slice);
+	auto slice = *base::take(_chatProcess->slice);
 	if (!slice.list.empty()) {
-		process->offsetId = slice.list.back().id + 1;
-		_dialogsProcess->sliceOne(std::move(slice));
+		_chatProcess->offsetId = slice.list.back().id + 1;
+		_chatProcess->handleSlice(std::move(slice));
 	}
-	if (process->lastSlice) {
+	if (_chatProcess->lastSlice) {
 		finishMessages();
 	} else {
 		requestMessagesSlice();
@@ -702,35 +818,22 @@ void ApiWrap::finishMessagesSlice() {
 }
 
 void ApiWrap::loadMessageFileDone(const QString &relativePath) {
-	Expects(_dialogsProcess != nullptr);
-	Expects(_dialogsProcess->single != nullptr);
-	Expects(_dialogsProcess->single->slice.has_value());
-	Expects((_dialogsProcess->single->fileIndex >= 0)
-		&& (_dialogsProcess->single->fileIndex
-			< _dialogsProcess->single->slice->list.size()));
+	Expects(_chatProcess != nullptr);
+	Expects(_chatProcess->slice.has_value());
+	Expects((_chatProcess->fileIndex >= 0)
+		&& (_chatProcess->fileIndex < _chatProcess->slice->list.size()));
 
-	const auto process = _dialogsProcess->single.get();
-	const auto index = process->fileIndex;
-	process->slice->list[index].file().relativePath = relativePath;
+	const auto index = _chatProcess->fileIndex;
+	_chatProcess->slice->list[index].file().relativePath = relativePath;
 	loadNextMessageFile();
 }
 
 void ApiWrap::finishMessages() {
-	Expects(_dialogsProcess != nullptr);
-	Expects(_dialogsProcess->single != nullptr);
-	Expects(!_dialogsProcess->single->slice.has_value());
+	Expects(_chatProcess != nullptr);
+	Expects(!_chatProcess->slice.has_value());
 
-	_dialogsProcess->single = nullptr;
-	_dialogsProcess->finishOne();
-
-	requestNextDialog();
-}
-
-void ApiWrap::finishDialogs() {
-	Expects(_dialogsProcess != nullptr);
-	Expects(_dialogsProcess->single == nullptr);
-
-	base::take(_dialogsProcess)->finish();
+	const auto process = base::take(_chatProcess);
+	process->done();
 }
 
 bool ApiWrap::processFileLoad(
@@ -800,7 +903,9 @@ bool ApiWrap::writePreloadedFile(Data::File &file) {
 	return false;
 }
 
-void ApiWrap::loadFile(const Data::File &file, FnMut<void(QString)> done) {
+void ApiWrap::loadFile(
+		const Data::File &file,
+		FnMut<void(QString)> done) {
 	Expects(_fileProcess == nullptr);
 	Expects(file.location.dcId != 0);
 
diff --git a/Telegram/SourceFiles/export/export_api_wrap.h b/Telegram/SourceFiles/export/export_api_wrap.h
index 9b9a4b0f2b..e101e420b8 100644
--- a/Telegram/SourceFiles/export/export_api_wrap.h
+++ b/Telegram/SourceFiles/export/export_api_wrap.h
@@ -33,9 +33,20 @@ public:
 
 	rpl::producer<RPCError> errors() const;
 
+	struct StartInfo {
+		int userpicsCount = 0;
+		int dialogsCount = 0;
+		int leftChannelsCount = 0;
+	};
 	void startExport(
 		const Settings &settings,
-		FnMut<void()> done);
+		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 requestPersonalInfo(FnMut<void(Data::PersonalInfo&&)> done);
 
@@ -48,22 +59,28 @@ public:
 
 	void requestSessions(FnMut<void(Data::SessionsList&&)> done);
 
-	void requestDialogs(
-		FnMut<void(const Data::DialogsInfo&)> start,
-		Fn<void(const Data::DialogInfo&)> startOne,
-		Fn<void(Data::MessagesSlice&&)> sliceOne,
-		Fn<void()> finishOne,
-		FnMut<void()> finish);
+	void requestMessages(
+		const Data::DialogInfo &info,
+		Fn<void(Data::MessagesSlice&&)> slice,
+		FnMut<void()> done);
 
 	~ApiWrap();
 
 private:
+	class LoadedFileCache;
+	struct StartProcess;
 	struct UserpicsProcess;
 	struct FileProcess;
+	struct LeftChannelsProcess;
 	struct DialogsProcess;
-	class LoadedFileCache;
+	struct ChatProcess;
 
 	void startMainSession(FnMut<void()> done);
+	void sendNextStartRequest();
+	void requestUserpicsCount();
+	void requestDialogsCount();
+	void requestLeftChannelsCount();
+	void finishStartProcess();
 
 	void handleUserpicsSlice(const MTPphotos_Photos &result);
 	void loadUserpicsFiles(Data::UserpicsSlice &&slice);
@@ -73,18 +90,20 @@ private:
 
 	void requestDialogsSlice();
 	void appendDialogsSlice(Data::DialogsInfo &&info);
-	void requestLeftChannels();
-	void requestLeftDialog();
 	void finishDialogsList();
 
-	void requestNextDialog();
+	void requestLeftChannelsSliceGeneric(FnMut<void()> done);
+	void requestLeftChannelsSlice();
+	void appendLeftChannelsSlice(Data::DialogsInfo &&info);
+
+	void appendChatsSlice(Data::DialogsInfo &to, Data::DialogsInfo &&info);
+
 	void requestMessagesSlice();
 	void loadMessagesFiles(Data::MessagesSlice &&slice);
 	void loadNextMessageFile();
 	void loadMessageFileDone(const QString &relativePath);
 	void finishMessagesSlice();
 	void finishMessages();
-	void finishDialogs();
 
 	bool processFileLoad(
 		Data::File &file,
@@ -93,7 +112,9 @@ private:
 	std::unique_ptr<FileProcess> prepareFileProcess(
 		const Data::File &file) const;
 	bool writePreloadedFile(Data::File &file);
-	void loadFile(const Data::File &file, FnMut<void(QString)> done);
+	void loadFile(
+		const Data::File &file,
+		FnMut<void(QString)> done);
 	void loadFilePart();
 	void filePartDone(int offset, const MTPupload_File &result);
 
@@ -113,10 +134,13 @@ private:
 	std::unique_ptr<Settings> _settings;
 	MTPInputUser _user = MTP_inputUserSelf();
 
+	std::unique_ptr<StartProcess> _startProcess;
 	std::unique_ptr<LoadedFileCache> _fileCache;
 	std::unique_ptr<UserpicsProcess> _userpicsProcess;
 	std::unique_ptr<FileProcess> _fileProcess;
+	std::unique_ptr<LeftChannelsProcess> _leftChannelsProcess;
 	std::unique_ptr<DialogsProcess> _dialogsProcess;
+	std::unique_ptr<ChatProcess> _chatProcess;
 
 	rpl::event_stream<RPCError> _errors;
 
diff --git a/Telegram/SourceFiles/export/export_controller.cpp b/Telegram/SourceFiles/export/export_controller.cpp
index df5f1b4658..c590b1375b 100644
--- a/Telegram/SourceFiles/export/export_controller.cpp
+++ b/Telegram/SourceFiles/export/export_controller.cpp
@@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 
 namespace Export {
 
+auto kNullStateCallback = [](ProcessingState&) {};
+
 class Controller {
 public:
 	Controller(crl::weak_on_queue<Controller> weak);
@@ -21,11 +23,11 @@ public:
 	rpl::producer<State> state() const;
 
 	// Password step.
-	void submitPassword(const QString &password);
-	void requestPasswordRecover();
-	rpl::producer<PasswordUpdate> passwordUpdate() const;
-	void reloadPasswordState();
-	void cancelUnconfirmedPassword();
+	//void submitPassword(const QString &password);
+	//void requestPasswordRecover();
+	//rpl::producer<PasswordUpdate> passwordUpdate() const;
+	//void reloadPasswordState();
+	//void cancelUnconfirmedPassword();
 
 	// Processing step.
 	void startExport(const Settings &settings);
@@ -37,30 +39,56 @@ private:
 	void ioError(const QString &path);
 	void setFinishedState();
 
-	void requestPasswordState();
-	void passwordStateDone(const MTPaccount_Password &password);
+	//void requestPasswordState();
+	//void passwordStateDone(const MTPaccount_Password &password);
 
 	void fillExportSteps();
+	void fillSubstepsInSteps(const ApiWrap::StartInfo &info);
 	void exportNext();
+	void initialize();
+	void collectLeftChannels();
+	void collectDialogsList();
 	void exportPersonalInfo();
 	void exportUserpics();
 	void exportContacts();
 	void exportSessions();
 	void exportDialogs();
+	void exportNextDialog();
+	void exportLeftChannels();
+	void exportNextLeftChannel();
+
+	template <typename Callback = const decltype(kNullStateCallback) &>
+	ProcessingState prepareState(
+		Step step,
+		Callback &&callback = kNullStateCallback) const;
+	ProcessingState stateInitializing() const;
+	ProcessingState stateLeftChannelsList(int processed) const;
+	ProcessingState stateDialogsList(int processed) const;
+	ProcessingState statePersonalInfo() const;
+	ProcessingState stateUserpics() const;
+	ProcessingState stateContacts() const;
+	ProcessingState stateSessions() const;
+	ProcessingState stateLeftChannels() const;
+	ProcessingState stateDialogs() const;
+
+	int substepsInStep(Step step) const;
 
 	bool normalizePath();
 
 	ApiWrap _api;
 	Settings _settings;
+	Data::DialogsInfo _leftChannelsInfo;
+	Data::DialogsInfo _dialogsInfo;
+	int _leftChannelIndex = -1;
+	int _dialogIndex = -1;
 
 	// rpl::variable<State> fails to compile in MSVC :(
 	State _state;
 	rpl::event_stream<State> _stateChanges;
-
-	mtpRequestId _passwordRequestId = 0;
+	std::vector<int> _substepsInStep;
 
 	std::unique_ptr<Output::AbstractWriter> _writer;
-	std::vector<ProcessingState::Step> _steps;
+	std::vector<Step> _steps;
 	int _stepIndex = -1;
 
 	rpl::lifetime _lifetime;
@@ -102,49 +130,49 @@ void Controller::ioError(const QString &path) {
 	setState(ErrorState{ ErrorState::Type::IO, base::none, path });
 }
 
-void Controller::submitPassword(const QString &password) {
-
-}
-
-void Controller::requestPasswordRecover() {
-
-}
-
-rpl::producer<PasswordUpdate> Controller::passwordUpdate() const {
-	return rpl::never<PasswordUpdate>();
-}
-
-void Controller::reloadPasswordState() {
-	//_mtp.request(base::take(_passwordRequestId)).cancel();
-	requestPasswordState();
-}
-
-void Controller::requestPasswordState() {
-	if (_passwordRequestId) {
-		return;
-	}
-	//_passwordRequestId = _mtp.request(MTPaccount_GetPassword(
-	//)).done([=](const MTPaccount_Password &result) {
-	//	_passwordRequestId = 0;
-	//	passwordStateDone(result);
-	//}).fail([=](const RPCError &error) {
-	//	apiError(error);
-	//}).send();
-}
-
-void Controller::passwordStateDone(const MTPaccount_Password &result) {
-	auto state = PasswordCheckState();
-	state.checked = false;
-	state.requesting = false;
-	state.hasPassword;
-	state.hint;
-	state.unconfirmedPattern;
-	setState(std::move(state));
-}
-
-void Controller::cancelUnconfirmedPassword() {
-
-}
+//void Controller::submitPassword(const QString &password) {
+//
+//}
+//
+//void Controller::requestPasswordRecover() {
+//
+//}
+//
+//rpl::producer<PasswordUpdate> Controller::passwordUpdate() const {
+//	return rpl::never<PasswordUpdate>();
+//}
+//
+//void Controller::reloadPasswordState() {
+//	//_mtp.request(base::take(_passwordRequestId)).cancel();
+//	requestPasswordState();
+//}
+//
+//void Controller::requestPasswordState() {
+//	if (_passwordRequestId) {
+//		return;
+//	}
+//	//_passwordRequestId = _mtp.request(MTPaccount_GetPassword(
+//	//)).done([=](const MTPaccount_Password &result) {
+//	//	_passwordRequestId = 0;
+//	//	passwordStateDone(result);
+//	//}).fail([=](const RPCError &error) {
+//	//	apiError(error);
+//	//}).send();
+//}
+//
+//void Controller::passwordStateDone(const MTPaccount_Password &result) {
+//	auto state = PasswordCheckState();
+//	state.checked = false;
+//	state.requesting = false;
+//	state.hasPassword;
+//	state.hint;
+//	state.unconfirmedPattern;
+//	setState(std::move(state));
+//}
+//
+//void Controller::cancelUnconfirmedPassword() {
+//
+//}
 
 void Controller::startExport(const Settings &settings) {
 	if (!_settings.path.isEmpty()) {
@@ -158,9 +186,7 @@ void Controller::startExport(const Settings &settings) {
 	}
 	_writer = Output::CreateWriter(_settings.format);
 	fillExportSteps();
-	_api.startExport(_settings, [=] {
-		exportNext();
-	});
+	exportNext();
 }
 
 bool Controller::normalizePath() {
@@ -195,6 +221,13 @@ bool Controller::normalizePath() {
 
 void Controller::fillExportSteps() {
 	using Type = Settings::Type;
+	_steps.push_back(Step::Initializing);
+	if (_settings.types & Type::GroupsChannelsMask) {
+		_steps.push_back(Step::LeftChannels);
+	}
+	if (_settings.types & Type::AnyChatsMask) {
+		_steps.push_back(Step::DialogsList);
+	}
 	if (_settings.types & Type::PersonalInfo) {
 		_steps.push_back(Step::PersonalInfo);
 	}
@@ -207,17 +240,46 @@ void Controller::fillExportSteps() {
 	if (_settings.types & Type::Sessions) {
 		_steps.push_back(Step::Sessions);
 	}
-	const auto dialogTypes = Type::PersonalChats
-		| Type::BotChats
-		| Type::PrivateGroups
-		| Type::PublicGroups
-		| Type::PrivateChannels
-		| Type::PublicChannels;
-	if (_settings.types & dialogTypes) {
+	if (_settings.types & Type::AnyChatsMask) {
 		_steps.push_back(Step::Dialogs);
 	}
 }
 
+void Controller::fillSubstepsInSteps(const ApiWrap::StartInfo &info) {
+	const auto push = [&](Step step, int count) {
+		const auto index = static_cast<int>(step);
+		if (index >= _substepsInStep.size()) {
+			_substepsInStep.resize(index + 1, 0);
+		}
+		_substepsInStep[index] = count;
+	};
+	push(Step::Initializing, 1);
+	if (_settings.types & Settings::Type::GroupsChannelsMask) {
+		push(Step::LeftChannelsList, 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) {
+		push(Step::Userpics, info.userpicsCount);
+	}
+	if (_settings.types & Settings::Type::Contacts) {
+		push(Step::Contacts, 1);
+	}
+	if (_settings.types & Settings::Type::Sessions) {
+		push(Step::Sessions, 1);
+	}
+	if (_settings.types & Settings::Type::GroupsChannelsMask) {
+		push(Step::LeftChannels, info.leftChannelsCount);
+	}
+	if (_settings.types & Settings::Type::AnyChatsMask) {
+		push(Step::Dialogs, info.dialogsCount);
+	}
+}
+
 void Controller::exportNext() {
 	if (!++_stepIndex) {
 		_writer->start(_settings);
@@ -229,15 +291,52 @@ void Controller::exportNext() {
 	}
 	const auto step = _steps[_stepIndex];
 	switch (step) {
+	case Step::Initializing: return initialize();
+	case Step::LeftChannelsList: return collectLeftChannels();
+	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();
+	case Step::LeftChannels: return exportLeftChannels();
 	case Step::Dialogs: return exportDialogs();
 	}
 	Unexpected("Step in Controller::exportNext.");
 }
 
+void Controller::initialize() {
+	setState(stateInitializing());
+
+	_api.startExport(_settings, [=](ApiWrap::StartInfo info) {
+		fillSubstepsInSteps(info);
+		exportNext();
+	});
+}
+
+void Controller::collectLeftChannels() {
+	_api.requestLeftChannelsList([=](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) {
+		_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);
@@ -271,20 +370,123 @@ void Controller::exportSessions() {
 }
 
 void Controller::exportDialogs() {
-	_api.requestDialogs([=](const Data::DialogsInfo &result) {
-		_writer->writeDialogsStart(result);
-	}, [=](const Data::DialogInfo &result) {
-		_writer->writeDialogStart(result);
-	}, [=](Data::MessagesSlice &&result) {
-		_writer->writeMessagesSlice(result);
-	}, [=] {
-		_writer->writeDialogEnd();
-	}, [=] {
-		_writer->writeDialogsEnd();
-		exportNext();
+	_writer->writeDialogsStart(_dialogsInfo);
+
+	exportNextDialog();
+}
+
+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);
+		}, [=] {
+			_writer->writeDialogEnd();
+			exportNextDialog();
+		});
+		return;
+	}
+	_writer->writeDialogsEnd();
+	exportNext();
+}
+
+void Controller::exportLeftChannels() {
+	_writer->writeLeftChannelsStart(_leftChannelsInfo);
+
+	exportNextLeftChannel();
+}
+
+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);
+		}, [=] {
+			_writer->writeLeftChannelEnd();
+			exportNextLeftChannel();
+		});
+		return;
+	}
+	_writer->writeLeftChannelsEnd();
+	exportNext();
+}
+
+template <typename Callback>
+ProcessingState Controller::prepareState(
+		Step step,
+		Callback &&callback) const {
+	auto result = ProcessingState();
+	callback(result);
+	result.step = step;
+	result.substepsInStep = _substepsInStep;
+	return result;
+}
+
+ProcessingState Controller::stateInitializing() const {
+	return ProcessingState();
+}
+
+ProcessingState Controller::stateLeftChannelsList(int processed) const {
+	const auto step = Step::LeftChannelsList;
+	return prepareState(step, [&](ProcessingState &result) {
+		result.entityIndex = processed;
+		result.entityCount = std::max(processed, substepsInStep(step));
 	});
 }
 
+ProcessingState Controller::stateDialogsList(int processed) const {
+	const auto step = Step::DialogsList;
+	return prepareState(step, [&](ProcessingState &result) {
+		result.entityIndex = processed;
+		result.entityCount = std::max(processed, substepsInStep(step));
+	});
+}
+ProcessingState Controller::statePersonalInfo() const {
+	return prepareState(Step::PersonalInfo);
+}
+
+ProcessingState Controller::stateUserpics() const {
+	return prepareState(Step::Userpics, [&](ProcessingState &result) {
+
+	});
+}
+
+ProcessingState Controller::stateContacts() const {
+	return prepareState(Step::Contacts);
+}
+
+ProcessingState Controller::stateSessions() const {
+	return prepareState(Step::Sessions);
+}
+
+ProcessingState Controller::stateLeftChannels() const {
+	const auto step = Step::LeftChannels;
+	return prepareState(step, [&](ProcessingState &result) {
+		//result.entityIndex = processed;
+		//result.entityCount = std::max(processed, substepsInStep(step));
+	});
+}
+
+ProcessingState Controller::stateDialogs() const {
+	const auto step = Step::Dialogs;
+	return prepareState(step, [&](ProcessingState &result) {
+		//result.entityIndex = processed;
+		//result.entityCount = std::max(processed, substepsInStep(step));
+	});
+}
+
+int Controller::substepsInStep(Step step) const {
+	Expects(_substepsInStep.size() > static_cast<int>(step));
+
+	return _substepsInStep[static_cast<int>(step)];
+}
+
 void Controller::setFinishedState() {
 	setState(FinishedState{ _writer->mainFilePath() });
 }
@@ -298,35 +500,35 @@ rpl::producer<State> ControllerWrap::state() const {
 	});
 }
 
-void ControllerWrap::submitPassword(const QString &password) {
-	_wrapped.with([=](Controller &controller) {
-		controller.submitPassword(password);
-	});
-}
-
-void ControllerWrap::requestPasswordRecover() {
-	_wrapped.with([=](Controller &controller) {
-		controller.requestPasswordRecover();
-	});
-}
-
-rpl::producer<PasswordUpdate> ControllerWrap::passwordUpdate() const {
-	return _wrapped.producer_on_main([=](const Controller &controller) {
-		return controller.passwordUpdate();
-	});
-}
-
-void ControllerWrap::reloadPasswordState() {
-	_wrapped.with([=](Controller &controller) {
-		controller.reloadPasswordState();
-	});
-}
-
-void ControllerWrap::cancelUnconfirmedPassword() {
-	_wrapped.with([=](Controller &controller) {
-		controller.cancelUnconfirmedPassword();
-	});
-}
+//void ControllerWrap::submitPassword(const QString &password) {
+//	_wrapped.with([=](Controller &controller) {
+//		controller.submitPassword(password);
+//	});
+//}
+//
+//void ControllerWrap::requestPasswordRecover() {
+//	_wrapped.with([=](Controller &controller) {
+//		controller.requestPasswordRecover();
+//	});
+//}
+//
+//rpl::producer<PasswordUpdate> ControllerWrap::passwordUpdate() const {
+//	return _wrapped.producer_on_main([=](const Controller &controller) {
+//		return controller.passwordUpdate();
+//	});
+//}
+//
+//void ControllerWrap::reloadPasswordState() {
+//	_wrapped.with([=](Controller &controller) {
+//		controller.reloadPasswordState();
+//	});
+//}
+//
+//void ControllerWrap::cancelUnconfirmedPassword() {
+//	_wrapped.with([=](Controller &controller) {
+//		controller.cancelUnconfirmedPassword();
+//	});
+//}
 
 void ControllerWrap::startExport(const Settings &settings) {
 	LOG(("Export Info: Started export to '%1'.").arg(settings.path));
diff --git a/Telegram/SourceFiles/export/export_controller.h b/Telegram/SourceFiles/export/export_controller.h
index 73ab039ce4..22b8929583 100644
--- a/Telegram/SourceFiles/export/export_controller.h
+++ b/Telegram/SourceFiles/export/export_controller.h
@@ -27,20 +27,30 @@ struct PasswordCheckState {
 
 struct ProcessingState {
 	enum class Step {
+		Initializing,
+		LeftChannelsList,
+		DialogsList,
 		PersonalInfo,
 		Userpics,
 		Contacts,
 		Sessions,
+		LeftChannels,
 		Dialogs,
 	};
 	enum class Item {
 		Other,
 		Photo,
 		Video,
+		VoiceMessage,
+		VideoMessage,
+		Sticker,
+		GIF,
 		File,
 	};
 
-	Step step = Step::PersonalInfo;
+	Step step = Step::Initializing;
+
+	std::vector<int> substepsInStep;
 
 	int entityIndex = 0;
 	int entityCount = 1;
@@ -50,9 +60,11 @@ struct ProcessingState {
 	int itemCount = 0;
 	Item itemType = Item::Other;
 	QString itemName;
+	QString itemId;
 
 	int bytesLoaded = 0;
 	int bytesCount = 0;
+	QString objectId;
 
 };
 
@@ -79,16 +91,16 @@ using State = base::optional_variant<
 	ErrorState,
 	FinishedState>;
 
-struct PasswordUpdate {
-	enum class Type {
-		CheckSucceed,
-		WrongPassword,
-		FloodLimit,
-		RecoverUnavailable,
-	};
-	Type type = Type::WrongPassword;
-
-};
+//struct PasswordUpdate {
+//	enum class Type {
+//		CheckSucceed,
+//		WrongPassword,
+//		FloodLimit,
+//		RecoverUnavailable,
+//	};
+//	Type type = Type::WrongPassword;
+//
+//};
 
 class ControllerWrap {
 public:
@@ -97,11 +109,11 @@ public:
 	rpl::producer<State> state() const;
 
 	// Password step.
-	void submitPassword(const QString &password);
-	void requestPasswordRecover();
-	rpl::producer<PasswordUpdate> passwordUpdate() const;
-	void reloadPasswordState();
-	void cancelUnconfirmedPassword();
+	//void submitPassword(const QString &password);
+	//void requestPasswordRecover();
+	//rpl::producer<PasswordUpdate> passwordUpdate() const;
+	//void reloadPasswordState();
+	//void cancelUnconfirmedPassword();
 
 	// Processing step.
 	void startExport(const Settings &settings);
diff --git a/Telegram/SourceFiles/export/export_settings.h b/Telegram/SourceFiles/export/export_settings.h
index 91dcf0e204..618ab4de4c 100644
--- a/Telegram/SourceFiles/export/export_settings.h
+++ b/Telegram/SourceFiles/export/export_settings.h
@@ -39,16 +39,21 @@ struct MediaSettings {
 
 struct Settings {
 	enum class Type {
-		PersonalInfo    = 0x001,
-		Userpics        = 0x002,
-		Contacts        = 0x004,
-		Sessions        = 0x008,
-		PersonalChats   = 0x010,
-		BotChats        = 0x020,
-		PrivateGroups   = 0x040,
-		PublicGroups    = 0x080,
-		PrivateChannels = 0x100,
-		PublicChannels  = 0x200,
+		PersonalInfo       = 0x001,
+		Userpics           = 0x002,
+		Contacts           = 0x004,
+		Sessions           = 0x008,
+		PersonalChats      = 0x010,
+		BotChats           = 0x020,
+		PrivateGroups      = 0x040,
+		PublicGroups       = 0x080,
+		PrivateChannels    = 0x100,
+		PublicChannels     = 0x200,
+
+		GroupsMask         = PrivateGroups | PublicGroups,
+		ChannelsMask       = PrivateChannels | PublicChannels,
+		GroupsChannelsMask = GroupsMask | ChannelsMask,
+		AnyChatsMask       = PersonalChats | BotChats | GroupsChannelsMask,
 	};
 	using Types = base::flags<Type>;
 	friend inline constexpr auto is_flag_type(Type) { return true; };
diff --git a/Telegram/SourceFiles/export/output/export_output_abstract.h b/Telegram/SourceFiles/export/output/export_output_abstract.h
index 3d0bfd65b4..fbd2ba696b 100644
--- a/Telegram/SourceFiles/export/output/export_output_abstract.h
+++ b/Telegram/SourceFiles/export/output/export_output_abstract.h
@@ -47,10 +47,16 @@ public:
 
 	virtual bool writeDialogsStart(const Data::DialogsInfo &data) = 0;
 	virtual bool writeDialogStart(const Data::DialogInfo &data) = 0;
-	virtual bool writeMessagesSlice(const Data::MessagesSlice &data) = 0;
+	virtual bool writeDialogSlice(const Data::MessagesSlice &data) = 0;
 	virtual bool writeDialogEnd() = 0;
 	virtual bool 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;
+
 	virtual bool finish() = 0;
 
 	virtual QString mainFilePath() = 0;
diff --git a/Telegram/SourceFiles/export/output/export_output_file.cpp b/Telegram/SourceFiles/export/output/export_output_file.cpp
index 539c8ab275..e415b76524 100644
--- a/Telegram/SourceFiles/export/output/export_output_file.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_file.cpp
@@ -18,6 +18,10 @@ namespace Output {
 File::File(const QString &path) : _path(path) {
 }
 
+int File::size() const {
+	return _offset;
+}
+
 bool File::empty() const {
 	return !_offset;
 }
diff --git a/Telegram/SourceFiles/export/output/export_output_file.h b/Telegram/SourceFiles/export/output/export_output_file.h
index 680b25e2d2..32907682ee 100644
--- a/Telegram/SourceFiles/export/output/export_output_file.h
+++ b/Telegram/SourceFiles/export/output/export_output_file.h
@@ -20,6 +20,7 @@ class File {
 public:
 	File(const QString &path);
 
+	int size() const;
 	bool empty() const;
 
 	enum class Result {
diff --git a/Telegram/SourceFiles/export/output/export_output_text.cpp b/Telegram/SourceFiles/export/output/export_output_text.cpp
index 23d2db12c5..03ccec333b 100644
--- a/Telegram/SourceFiles/export/output/export_output_text.cpp
+++ b/Telegram/SourceFiles/export/output/export_output_text.cpp
@@ -582,6 +582,49 @@ bool TextWriter::writeSessionsList(const Data::SessionsList &data) {
 }
 
 bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
+	return writeChatsStart(data, "Chats", "chats.txt");
+}
+
+bool TextWriter::writeDialogStart(const Data::DialogInfo &data) {
+	return writeChatStart(data);
+}
+
+bool TextWriter::writeDialogSlice(const Data::MessagesSlice &data) {
+	return writeChatSlice(data);
+}
+
+bool TextWriter::writeDialogEnd() {
+	return writeChatEnd();
+}
+
+bool TextWriter::writeDialogsEnd() {
+	return true;
+}
+
+bool TextWriter::writeLeftChannelsStart(const Data::DialogsInfo &data) {
+	return writeChatsStart(data, "Left chats", "left_chats.txt");
+}
+
+bool TextWriter::writeLeftChannelStart(const Data::DialogInfo &data) {
+	return writeChatStart(data);
+}
+
+bool TextWriter::writeLeftChannelSlice(const Data::MessagesSlice &data) {
+	return writeChatSlice(data);
+}
+
+bool TextWriter::writeLeftChannelEnd() {
+	return writeChatEnd();
+}
+
+bool TextWriter::writeLeftChannelsEnd() {
+	return true;
+}
+
+bool TextWriter::writeChatsStart(
+		const Data::DialogsInfo &data,
+		const QByteArray &listName,
+		const QString &fileName) {
 	Expects(_result != nullptr);
 
 	if (data.list.empty()) {
@@ -620,7 +663,7 @@ bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
 		}
 		Unexpected("Dialog type in TypeString.");
 	};
-	const auto file = fileWithRelativePath("chats.txt");
+	const auto file = fileWithRelativePath(fileName);
 	auto list = std::vector<QByteArray>();
 	list.reserve(data.list.size());
 	auto index = 0;
@@ -637,27 +680,28 @@ bool TextWriter::writeDialogsStart(const Data::DialogsInfo &data) {
 		return false;
 	}
 
-	const auto header = "Chats "
-		"(" + Data::NumberToString(data.list.size()) + ") - chats.txt"
+	const auto header = listName + " "
+		"(" + Data::NumberToString(data.list.size()) + ") - "
+		+ fileName.toUtf8()
 		+ kLineBreak
 		+ kLineBreak;
 	return _result->writeBlock(header) == File::Result::Success;
 }
 
-bool TextWriter::writeDialogStart(const Data::DialogInfo &data) {
-	Expects(_dialog == nullptr);
+bool TextWriter::writeChatStart(const Data::DialogInfo &data) {
+	Expects(_chat == nullptr);
 	Expects(_dialogIndex < _dialogsCount);
 
 	const auto digits = Data::NumberToString(_dialogsCount - 1).size();
 	const auto number = Data::NumberToString(++_dialogIndex, digits, '0');
-	_dialog = fileWithRelativePath(data.relativePath + "messages.txt");
+	_chat = fileWithRelativePath(data.relativePath + "messages.txt");
 	_dialogEmpty = true;
 	_dialogOnlyMy = data.onlyMyMessages;
 	return true;
 }
 
-bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) {
-	Expects(_dialog != nullptr);
+bool TextWriter::writeChatSlice(const Data::MessagesSlice &data) {
+	Expects(_chat != nullptr);
 	Expects(!data.list.empty());
 
 	_dialogEmpty = false;
@@ -670,25 +714,21 @@ bool TextWriter::writeMessagesSlice(const Data::MessagesSlice &data) {
 			data.peers,
 			_settings.internalLinksDomain));
 	}
-	const auto full = _dialog->empty()
+	const auto full = _chat->empty()
 		? JoinList(kLineBreak, list)
 		: kLineBreak + JoinList(kLineBreak, list);
-	return _dialog->writeBlock(full) == File::Result::Success;
+	return _chat->writeBlock(full) == File::Result::Success;
 }
 
-bool TextWriter::writeDialogEnd() {
-	Expects(_dialog != nullptr);
+bool TextWriter::writeChatEnd() {
+	Expects(_chat != nullptr);
 
 	if (_dialogEmpty) {
-		_dialog->writeBlock(_dialogOnlyMy
+		_chat->writeBlock(_dialogOnlyMy
 			? "No outgoing messages in this chat."
 			: "No messages in this chat.");
 	}
-	_dialog = nullptr;
-	return true;
-}
-
-bool TextWriter::writeDialogsEnd() {
+	_chat = nullptr;
 	return true;
 }
 
diff --git a/Telegram/SourceFiles/export/output/export_output_text.h b/Telegram/SourceFiles/export/output/export_output_text.h
index d6f20ccdf7..cdb994eaf3 100644
--- a/Telegram/SourceFiles/export/output/export_output_text.h
+++ b/Telegram/SourceFiles/export/output/export_output_text.h
@@ -30,10 +30,16 @@ public:
 
 	bool writeDialogsStart(const Data::DialogsInfo &data) override;
 	bool writeDialogStart(const Data::DialogInfo &data) override;
-	bool writeMessagesSlice(const Data::MessagesSlice &data) override;
+	bool writeDialogSlice(const Data::MessagesSlice &data) override;
 	bool writeDialogEnd() override;
 	bool 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;
+
 	bool finish() override;
 
 	QString mainFilePath() override;
@@ -43,6 +49,14 @@ private:
 	QString pathWithRelativePath(const QString &path) const;
 	std::unique_ptr<File> fileWithRelativePath(const QString &path) const;
 
+	bool 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();
+
 	Settings _settings;
 
 	std::unique_ptr<File> _result;
@@ -52,7 +66,12 @@ private:
 	int _dialogIndex = 0;
 	bool _dialogOnlyMy = false;
 	bool _dialogEmpty = true;
-	std::unique_ptr<File> _dialog;
+
+	int _leftChannelsCount = 0;
+	int _leftChannelIndex = 0;
+	bool _leftChannelEmpty = true;
+
+	std::unique_ptr<File> _chat;
 
 };