/* 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 <rpl/details/callable.h> #include "base/bytes.h" #include "base/weak_ptr.h" #include "base/flat_map.h" #include "mtproto/core_types.h" #ifndef _DEBUG #define MTP_SENDER_USE_GENERIC_HANDLERS #endif // !_DEBUG class RPCError; namespace MTP { 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*>>>; using DoneHandler = FnMut<void( mtpRequestId requestId, bytes::const_span result)>; using FailHandler = FnMut<void( mtpRequestId requestId, RPCError &&error)>; struct Handlers { DoneHandler done; FailHandler 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, SecureRequest &&serialized) noexcept; void setToDC(ShiftedDcId dcId) noexcept; void setCanWait(TimeMs 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; SecureRequest _serialized; ShiftedDcId _dcId = 0; TimeMs _canWait = 0; Handlers _handlers; FailSkipPolicy _failSkipPolicy = FailSkipPolicy::Simple; mtpRequestId _afterRequestId = 0; }; public: ConcurrentSender(Fn<void(FnMut<void()>)> runner); template <typename Request> class SpecificRequestBuilder : public RequestBuilder { public: using Response = 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( TimeMs 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)> &&handler); [[nodiscard]] SpecificRequestBuilder &done(FnMut<void( mtpRequestId, Response &&)> &&handler); [[nodiscard]] SpecificRequestBuilder &done(FnMut<void( Response &&)> &&handler); [[nodiscard]] SpecificRequestBuilder &fail(FnMut<void()> &&handler); [[nodiscard]] SpecificRequestBuilder &fail(FnMut<void( mtpRequestId)> &&handler); [[nodiscard]] SpecificRequestBuilder &fail(FnMut<void( mtpRequestId, RPCError &&)> &&handler); [[nodiscard]] SpecificRequestBuilder &fail(FnMut<void( RPCError &&)> &&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 RPCDoneHandler; friend class RPCDoneHandler; class RPCFailHandler; friend class RPCFailHandler; friend class RequestBuilder; friend class SentRequestWrap; void senderRequestRegister(mtpRequestId requestId, Handlers &&handlers); void senderRequestDone( mtpRequestId requestId, bytes::const_span result); void senderRequestFail( mtpRequestId requestId, RPCError &&error); void senderRequestCancel(mtpRequestId requestId); void senderRequestCancelAll(); void senderRequestDetach(mtpRequestId requestId); const Fn<void(FnMut<void()>)> _runner; base::flat_map<mtpRequestId, Handlers> _requests; }; template <typename Response, 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); Response data; data.read(from, end); std::move(handler)(requestId, std::move(data)); }; } 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, SecureRequest::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( TimeMs 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(Response &&)> &&handler ) -> SpecificRequestBuilder & { setDoneHandler<Response>([handler = std::move(handler)]( mtpRequestId requestId, Response &&result) mutable { std::move(handler)(std::move(result)); }); return *this; } template <typename Request> auto ConcurrentSender::SpecificRequestBuilder<Request>::done( FnMut<void(mtpRequestId, Response &&)> &&handler ) -> SpecificRequestBuilder & { setDoneHandler<Response>(std::move(handler)); return *this; } template <typename Request> auto ConcurrentSender::SpecificRequestBuilder<Request>::done( FnMut<void(mtpRequestId)> &&handler ) -> SpecificRequestBuilder & { setDoneHandler<Response>([handler = std::move(handler)]( mtpRequestId requestId, Response &&result) mutable { std::move(handler)(requestId); }); return *this; } template <typename Request> auto ConcurrentSender::SpecificRequestBuilder<Request>::done( FnMut<void()> &&handler ) -> SpecificRequestBuilder & { setDoneHandler<Response>([handler = std::move(handler)]( mtpRequestId requestId, Response &&result) mutable { std::move(handler)(); }); return *this; } template <typename Request> auto ConcurrentSender::SpecificRequestBuilder<Request>::fail( FnMut<void(RPCError &&)> &&handler ) -> SpecificRequestBuilder & { setFailHandler([handler = std::move(handler)]( mtpRequestId requestId, RPCError &&error) mutable { std::move(handler)(std::move(error)); }); return *this; } template <typename Request> auto ConcurrentSender::SpecificRequestBuilder<Request>::fail( FnMut<void(mtpRequestId, RPCError &&)> &&handler ) -> SpecificRequestBuilder & { setFailHandler(std::move(handler)); return *this; } template <typename Request> auto ConcurrentSender::SpecificRequestBuilder<Request>::fail( FnMut<void(mtpRequestId)> &&handler ) -> SpecificRequestBuilder & { setFailHandler([handler = std::move(handler)]( mtpRequestId requestId, RPCError &&error) mutable { std::move(handler)(requestId); }); return *this; } template <typename Request> auto ConcurrentSender::SpecificRequestBuilder<Request>::fail( FnMut<void()> &&handler ) -> SpecificRequestBuilder & { setFailHandler([handler = std::move(handler)]( mtpRequestId requestId, RPCError &&error) mutable { std::move(handler)(); }); return *this; } #else // !MTP_SENDER_USE_GENERIC_HANDLERS template <typename Request> template <typename Handler> auto ConcurrentSender::SpecificRequestBuilder<Request>::done( Handler &&handler ) -> SpecificRequestBuilder & { using Response = typename Request::ResponseType; constexpr auto takesFull = rpl::details::is_callable_plain_v< Handler, mtpRequestId, Response>; constexpr auto takesResponse = rpl::details::is_callable_plain_v< Handler, Response>; constexpr auto takesRequestId = rpl::details::is_callable_plain_v< Handler, mtpRequestId>; constexpr auto takesNone = rpl::details::is_callable_plain_v<Handler>; if constexpr (takesFull) { setDoneHandler<Response>(std::forward<Handler>(handler)); } else if constexpr (takesResponse) { setDoneHandler<Response>([handler = std::forward<Handler>(handler)]( mtpRequestId requestId, Response &&result) mutable { std::move(handler)(std::move(result)); }); } else if constexpr (takesRequestId) { setDoneHandler<Response>([handler = std::forward<Handler>(handler)]( mtpRequestId requestId, Response &&result) mutable { std::move(handler)(requestId); }); } else if constexpr (takesNone) { setDoneHandler<Response>([handler = std::forward<Handler>(handler)]( mtpRequestId requestId, Response &&result) mutable { std::move(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, RPCError>; constexpr auto takesError = rpl::details::is_callable_plain_v< Handler, RPCError>; constexpr auto takesRequestId = rpl::details::is_callable_plain_v< Handler, mtpRequestId>; 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, RPCError &&error) mutable { std::move(handler)(std::move(error)); }); } else if constexpr (takesRequestId) { setFailHandler([handler = std::forward<Handler>(handler)]( mtpRequestId requestId, RPCError &&error) mutable { std::move(handler)(requestId); }); } else if constexpr (takesNone) { setFailHandler([handler = std::forward<Handler>(handler)]( mtpRequestId requestId, RPCError &&error) mutable { std::move(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