432 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			432 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
| This file is part of Telegram Desktop,
 | |
| the official desktop application for the Telegram messaging service.
 | |
| 
 | |
| For license and copyright information please follow this link:
 | |
| https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | |
| */
 | |
| #pragma once
 | |
| 
 | |
| #include "base/bytes.h"
 | |
| #include "base/weak_ptr.h"
 | |
| #include "base/flat_map.h"
 | |
| #include "mtproto/core_types.h"
 | |
| #include "mtproto/details/mtproto_serialized_request.h"
 | |
| 
 | |
| #include <QtCore/QPointer>
 | |
| #include <rpl/details/callable.h>
 | |
| 
 | |
| #ifndef _DEBUG
 | |
| #define MTP_SENDER_USE_GENERIC_HANDLERS
 | |
| #endif // !_DEBUG
 | |
| 
 | |
| 
 | |
| namespace MTP {
 | |
| 
 | |
| class Error;
 | |
| class Instance;
 | |
| 
 | |
| class ConcurrentSender : public base::has_weak_ptr {
 | |
| 	template <typename ...Args>
 | |
| 	static constexpr bool is_callable_v
 | |
| 		= rpl::details::is_callable_v<Args...>;
 | |
| 
 | |
| 	template <typename Method>
 | |
| 	auto with_instance(Method &&method)
 | |
| 	-> std::enable_if_t<is_callable_v<Method, not_null<Instance*>>>;
 | |
| 
 | |
| 	struct Handlers {
 | |
| 		FnMut<bool(mtpRequestId requestId, bytes::const_span result)> done;
 | |
| 		FnMut<void(mtpRequestId requestId, const Error &error)> fail;
 | |
| 	};
 | |
| 
 | |
| 	enum class FailSkipPolicy {
 | |
| 		Simple,
 | |
| 		HandleFlood,
 | |
| 		HandleAll,
 | |
| 	};
 | |
| 
 | |
| 	class RequestBuilder {
 | |
| 	public:
 | |
| 		RequestBuilder(const RequestBuilder &other) = delete;
 | |
| 		RequestBuilder(RequestBuilder &&other) = default;
 | |
| 		RequestBuilder &operator=(const RequestBuilder &other) = delete;
 | |
| 		RequestBuilder &operator=(RequestBuilder &&other) = delete;
 | |
| 
 | |
| 		mtpRequestId send();
 | |
| 
 | |
| 	protected:
 | |
| 		RequestBuilder(
 | |
| 			not_null<ConcurrentSender*> sender,
 | |
| 			details::SerializedRequest &&serialized) noexcept;
 | |
| 
 | |
| 		void setToDC(ShiftedDcId dcId) noexcept;
 | |
| 		void setCanWait(crl::time ms) noexcept;
 | |
| 		template <typename Response, typename InvokeFullDone>
 | |
| 		void setDoneHandler(InvokeFullDone &&invoke) noexcept;
 | |
| 		template <typename InvokeFullFail>
 | |
| 		void setFailHandler(InvokeFullFail &&invoke) noexcept;
 | |
| 		void setFailSkipPolicy(FailSkipPolicy policy) noexcept;
 | |
| 		void setAfter(mtpRequestId requestId) noexcept;
 | |
| 
 | |
| 	private:
 | |
| 		not_null<ConcurrentSender*> _sender;
 | |
| 		details::SerializedRequest _serialized;
 | |
| 		ShiftedDcId _dcId = 0;
 | |
| 		crl::time _canWait = 0;
 | |
| 
 | |
| 		Handlers _handlers;
 | |
| 		FailSkipPolicy _failSkipPolicy = FailSkipPolicy::Simple;
 | |
| 		mtpRequestId _afterRequestId = 0;
 | |
| 
 | |
| 	};
 | |
| 
 | |
| public:
 | |
| 	ConcurrentSender(
 | |
| 		QPointer<Instance> weak,
 | |
| 		Fn<void(FnMut<void()>)> runner);
 | |
| 
 | |
| 	template <typename Request>
 | |
| 	class SpecificRequestBuilder : public RequestBuilder {
 | |
| 	public:
 | |
| 		using Result = typename Request::ResponseType;
 | |
| 
 | |
| 		SpecificRequestBuilder(
 | |
| 			const SpecificRequestBuilder &other) = delete;
 | |
| 		SpecificRequestBuilder(
 | |
| 			SpecificRequestBuilder &&other) = default;
 | |
| 		SpecificRequestBuilder &operator=(
 | |
| 			const SpecificRequestBuilder &other) = delete;
 | |
| 		SpecificRequestBuilder &operator=(
 | |
| 			SpecificRequestBuilder &&other) = delete;
 | |
| 
 | |
| 		[[nodiscard]] SpecificRequestBuilder &toDC(
 | |
| 			ShiftedDcId dcId) noexcept;
 | |
| 		[[nodiscard]] SpecificRequestBuilder &afterDelay(
 | |
| 			crl::time ms) noexcept;
 | |
| 
 | |
| #ifndef MTP_SENDER_USE_GENERIC_HANDLERS
 | |
| 		// Allow code completion to show response type.
 | |
| 		[[nodiscard]] SpecificRequestBuilder &done(FnMut<void()> &&handler);
 | |
| 		[[nodiscard]] SpecificRequestBuilder &done(
 | |
| 			FnMut<void(mtpRequestId, Result &&)> &&handler);
 | |
| 		[[nodiscard]] SpecificRequestBuilder &done(
 | |
| 			FnMut<void(Result &&)> &&handler);
 | |
| 		[[nodiscard]] SpecificRequestBuilder &fail(Fn<void()> &&handler);
 | |
| 		[[nodiscard]] SpecificRequestBuilder &fail(
 | |
| 			Fn<void(mtpRequestId, const Error &)> &&handler);
 | |
| 		[[nodiscard]] SpecificRequestBuilder &fail(
 | |
| 			Fn<void(const Error &)> &&handler);
 | |
| #else // !MTP_SENDER_USE_GENERIC_HANDLERS
 | |
| 		template <typename Handler>
 | |
| 		[[nodiscard]] SpecificRequestBuilder &done(Handler &&handler);
 | |
| 		template <typename Handler>
 | |
| 		[[nodiscard]] SpecificRequestBuilder &fail(Handler &&handler);
 | |
| #endif // MTP_SENDER_USE_GENERIC_HANDLERS
 | |
| 
 | |
| 		[[nodiscard]] SpecificRequestBuilder &handleFloodErrors() noexcept;
 | |
| 		[[nodiscard]] SpecificRequestBuilder &handleAllErrors() noexcept;
 | |
| 		[[nodiscard]] SpecificRequestBuilder &afterRequest(
 | |
| 			mtpRequestId requestId) noexcept;
 | |
| 
 | |
| 	private:
 | |
| 		SpecificRequestBuilder(
 | |
| 			not_null<ConcurrentSender*> sender,
 | |
| 			Request &&request) noexcept;
 | |
| 
 | |
| 		friend class ConcurrentSender;
 | |
| 
 | |
| 	};
 | |
| 
 | |
| 	class SentRequestWrap {
 | |
| 	public:
 | |
| 		void cancel();
 | |
| 		void detach();
 | |
| 
 | |
| 	private:
 | |
| 		friend class ConcurrentSender;
 | |
| 		SentRequestWrap(
 | |
| 			not_null<ConcurrentSender*> sender,
 | |
| 			mtpRequestId requestId);
 | |
| 
 | |
| 		not_null<ConcurrentSender*> _sender;
 | |
| 		mtpRequestId _requestId = 0;
 | |
| 
 | |
| 	};
 | |
| 
 | |
| 	template <
 | |
| 		typename Request,
 | |
| 		typename = std::enable_if_t<!std::is_reference_v<Request>>,
 | |
| 		typename = typename Request::Unboxed>
 | |
| 	[[nodiscard]] SpecificRequestBuilder<Request> request(
 | |
| 		Request &&request) noexcept;
 | |
| 
 | |
| 	[[nodiscard]] SentRequestWrap request(mtpRequestId requestId) noexcept;
 | |
| 
 | |
| 	[[nodiscard]] auto requestCanceller() noexcept;
 | |
| 
 | |
| 	~ConcurrentSender();
 | |
| 
 | |
| private:
 | |
| 	class HandlerMaker;
 | |
| 	friend class HandlerMaker;
 | |
| 	friend class RequestBuilder;
 | |
| 	friend class SentRequestWrap;
 | |
| 
 | |
| 	void senderRequestRegister(mtpRequestId requestId, Handlers &&handlers);
 | |
| 	void senderRequestDone(
 | |
| 		mtpRequestId requestId,
 | |
| 		bytes::const_span result);
 | |
| 	void senderRequestFail(
 | |
| 		mtpRequestId requestId,
 | |
| 		const Error &error);
 | |
| 	void senderRequestCancel(mtpRequestId requestId);
 | |
| 	void senderRequestCancelAll();
 | |
| 	void senderRequestDetach(mtpRequestId requestId);
 | |
| 
 | |
| 	const QPointer<Instance> _weak;
 | |
| 	const Fn<void(FnMut<void()>)> _runner;
 | |
| 	base::flat_map<mtpRequestId, Handlers> _requests;
 | |
| 
 | |
| };
 | |
| 
 | |
| template <typename Result, typename InvokeFullDone>
 | |
| void ConcurrentSender::RequestBuilder::setDoneHandler(
 | |
| 	InvokeFullDone &&invoke
 | |
| ) noexcept {
 | |
| 	_handlers.done = [handler = std::move(invoke)](
 | |
| 			mtpRequestId requestId,
 | |
| 			bytes::const_span result) mutable {
 | |
| 		auto from = reinterpret_cast<const mtpPrime*>(result.data());
 | |
| 		const auto end = from + result.size() / sizeof(mtpPrime);
 | |
| 		Result data;
 | |
| 		if (!data.read(from, end)) {
 | |
| 			return false;
 | |
| 		}
 | |
| 		handler(requestId, std::move(data));
 | |
| 		return true;
 | |
| 	};
 | |
| }
 | |
| 
 | |
| template <typename InvokeFullFail>
 | |
| void ConcurrentSender::RequestBuilder::setFailHandler(
 | |
| 	InvokeFullFail &&invoke
 | |
| ) noexcept {
 | |
| 	_handlers.fail = std::move(invoke);
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| ConcurrentSender::SpecificRequestBuilder<Request>::SpecificRequestBuilder(
 | |
| 	not_null<ConcurrentSender*> sender,
 | |
| 	Request &&request) noexcept
 | |
| : RequestBuilder(sender, details::SerializedRequest::Serialize(request)) {
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::toDC(
 | |
| 	ShiftedDcId dcId
 | |
| ) noexcept -> SpecificRequestBuilder & {
 | |
| 	setToDC(dcId);
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::afterDelay(
 | |
| 	crl::time ms
 | |
| ) noexcept -> SpecificRequestBuilder & {
 | |
| 	setCanWait(ms);
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| #ifndef MTP_SENDER_USE_GENERIC_HANDLERS
 | |
| // Allow code completion to show response type.
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
 | |
| 	FnMut<void(Result &&)> &&handler
 | |
| ) -> SpecificRequestBuilder & {
 | |
| 	setDoneHandler<Result>([handler = std::move(handler)](
 | |
| 			mtpRequestId requestId,
 | |
| 			Result &&result) mutable {
 | |
| 		handler(std::move(result));
 | |
| 	});
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
 | |
| 	FnMut<void(mtpRequestId, Result &&)> &&handler
 | |
| ) -> SpecificRequestBuilder & {
 | |
| 	setDoneHandler<Result>(std::move(handler));
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
 | |
| 	FnMut<void()> &&handler
 | |
| ) -> SpecificRequestBuilder & {
 | |
| 	setDoneHandler<Result>([handler = std::move(handler)](
 | |
| 			mtpRequestId requestId,
 | |
| 			Result &&result) mutable {
 | |
| 		std::move(handler)();
 | |
| 	});
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
 | |
| 	Fn<void(const Error &)> &&handler
 | |
| ) -> SpecificRequestBuilder & {
 | |
| 	setFailHandler([handler = std::move(handler)](
 | |
| 			mtpRequestId requestId,
 | |
| 			const Error &error) {
 | |
| 		handler(error);
 | |
| 	});
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
 | |
| 	Fn<void(mtpRequestId, const Error &)> &&handler
 | |
| ) -> SpecificRequestBuilder & {
 | |
| 	setFailHandler(std::move(handler));
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
 | |
| 	Fn<void()> &&handler
 | |
| ) -> SpecificRequestBuilder & {
 | |
| 	setFailHandler([handler = std::move(handler)](
 | |
| 			mtpRequestId requestId,
 | |
| 			const Error &error) {
 | |
| 		handler();
 | |
| 	});
 | |
| 	return *this;
 | |
| }
 | |
| #else // !MTP_SENDER_USE_GENERIC_HANDLERS
 | |
| template <typename Request>
 | |
| template <typename Handler>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::done(
 | |
| 	Handler &&handler
 | |
| ) -> SpecificRequestBuilder & {
 | |
| 	using Result = typename Request::ResponseType;
 | |
| 	constexpr auto takesFull = rpl::details::is_callable_plain_v<
 | |
| 		Handler,
 | |
| 		mtpRequestId,
 | |
| 		Result>;
 | |
| 	constexpr auto takesResponse = rpl::details::is_callable_plain_v<
 | |
| 		Handler,
 | |
| 		Result>;
 | |
| 	constexpr auto takesNone = rpl::details::is_callable_plain_v<Handler>;
 | |
| 
 | |
| 	if constexpr (takesFull) {
 | |
| 		setDoneHandler<Result>(std::forward<Handler>(handler));
 | |
| 	} else if constexpr (takesResponse) {
 | |
| 		setDoneHandler<Result>([handler = std::forward<Handler>(handler)](
 | |
| 				mtpRequestId requestId,
 | |
| 				Result &&result) mutable {
 | |
| 			handler(std::move(result));
 | |
| 		});
 | |
| 	} else if constexpr (takesNone) {
 | |
| 		setDoneHandler<Result>([handler = std::forward<Handler>(handler)](
 | |
| 				mtpRequestId requestId,
 | |
| 				Result &&result) mutable {
 | |
| 			handler();
 | |
| 		});
 | |
| 	} else {
 | |
| 		static_assert(false_t(Handler{}), "Bad done handler.");
 | |
| 	}
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| template <typename Handler>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::fail(
 | |
| 	Handler &&handler
 | |
| ) -> SpecificRequestBuilder & {
 | |
| 	constexpr auto takesFull = rpl::details::is_callable_plain_v<
 | |
| 		Handler,
 | |
| 		mtpRequestId,
 | |
| 		Error>;
 | |
| 	constexpr auto takesError = rpl::details::is_callable_plain_v<
 | |
| 		Handler,
 | |
| 		Error>;
 | |
| 	constexpr auto takesNone = rpl::details::is_callable_plain_v<Handler>;
 | |
| 
 | |
| 	if constexpr (takesFull) {
 | |
| 		setFailHandler(std::forward<Handler>(handler));
 | |
| 	} else if constexpr (takesError) {
 | |
| 		setFailHandler([handler = std::forward<Handler>(handler)](
 | |
| 				mtpRequestId requestId,
 | |
| 				const Error &error) {
 | |
| 			handler(error);
 | |
| 		});
 | |
| 	} else if constexpr (takesNone) {
 | |
| 		setFailHandler([handler = std::forward<Handler>(handler)](
 | |
| 				mtpRequestId requestId,
 | |
| 				const Error &error) {
 | |
| 			handler();
 | |
| 		});
 | |
| 	} else {
 | |
| 		static_assert(false_t(Handler{}), "Bad fail handler.");
 | |
| 	}
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| #endif // MTP_SENDER_USE_GENERIC_HANDLERS
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::handleFloodErrors(
 | |
| ) noexcept -> SpecificRequestBuilder & {
 | |
| 	setFailSkipPolicy(FailSkipPolicy::HandleFlood);
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::handleAllErrors(
 | |
| ) noexcept -> SpecificRequestBuilder & {
 | |
| 	setFailSkipPolicy(FailSkipPolicy::HandleAll);
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| template <typename Request>
 | |
| auto ConcurrentSender::SpecificRequestBuilder<Request>::afterRequest(
 | |
| 	mtpRequestId requestId
 | |
| ) noexcept -> SpecificRequestBuilder & {
 | |
| 	setAfter(requestId);
 | |
| 	return *this;
 | |
| }
 | |
| 
 | |
| inline void ConcurrentSender::SentRequestWrap::cancel() {
 | |
| 	_sender->senderRequestCancel(_requestId);
 | |
| }
 | |
| 
 | |
| inline void ConcurrentSender::SentRequestWrap::detach() {
 | |
| 	_sender->senderRequestDetach(_requestId);
 | |
| }
 | |
| 
 | |
| inline ConcurrentSender::SentRequestWrap::SentRequestWrap(
 | |
| 	not_null<ConcurrentSender*> sender,
 | |
| 	mtpRequestId requestId
 | |
| ) : _sender(sender)
 | |
| , _requestId(requestId) {
 | |
| }
 | |
| 
 | |
| template <typename Request, typename, typename>
 | |
| inline auto ConcurrentSender::request(Request &&request) noexcept
 | |
| -> SpecificRequestBuilder<Request> {
 | |
| 	return SpecificRequestBuilder<Request>(this, std::move(request));
 | |
| }
 | |
| 
 | |
| inline auto ConcurrentSender::requestCanceller() noexcept {
 | |
| 	return [=](mtpRequestId requestId) {
 | |
| 		request(requestId).cancel();
 | |
| 	};
 | |
| }
 | |
| 
 | |
| inline auto ConcurrentSender::request(mtpRequestId requestId) noexcept
 | |
| -> SentRequestWrap {
 | |
| 	return SentRequestWrap(this, requestId);
 | |
| }
 | |
| 
 | |
| } // namespace MTP
 | 
