1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // Copyright (C) 2019 Alexey Edelev <semlanik@gmail.com> |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
4 | |
5 | #include <QtGrpc/private/qabstractgrpcchannel_p.h> |
6 | #include <QtGrpc/private/qtgrpclogging_p.h> |
7 | #include <QtGrpc/qgrpcclientbase.h> |
8 | #include <QtGrpc/qgrpcoperation.h> |
9 | |
10 | #include <QtProtobuf/qprotobufmessage.h> |
11 | #include <QtProtobuf/qprotobufserializer.h> |
12 | |
13 | #include <QtCore/private/qminimalflatset_p.h> |
14 | #include <QtCore/private/qobject_p.h> |
15 | #include <QtCore/qbytearray.h> |
16 | #include <QtCore/qlatin1stringview.h> |
17 | |
18 | #include <optional> |
19 | #include <type_traits> |
20 | |
21 | QT_BEGIN_NAMESPACE |
22 | |
23 | /*! |
24 | \class QGrpcClientBase |
25 | \inmodule QtGrpc |
26 | \brief The QGrpcClientBase class serves as base for generated client |
27 | interfaces. |
28 | |
29 | The QGrpcClientBase class provides a common set of functionalities for the |
30 | generated client interface of the \gRPC service definition. |
31 | |
32 | The RPC methods of this class should not be called directly. |
33 | |
34 | \note Thread safety is enforced for the non-const member functions. These |
35 | functions must be called from the same \l{QObject::} {thread} in which the |
36 | object was created. |
37 | */ |
38 | |
39 | /*! |
40 | \fn void QGrpcClientBase::channelChanged() |
41 | \since 6.7 |
42 | |
43 | Indicates that a new channel is attached to the client. |
44 | */ |
45 | |
46 | namespace { |
47 | template <typename Operation> |
48 | inline constexpr bool IsStream = false; |
49 | template <> |
50 | inline constexpr bool IsStream<QGrpcServerStream> = true; |
51 | template <> |
52 | inline constexpr bool IsStream<QGrpcClientStream> = true; |
53 | template <> |
54 | inline constexpr bool IsStream<QGrpcBidiStream> = true; |
55 | } |
56 | |
57 | class QGrpcClientBasePrivate : public QObjectPrivate |
58 | { |
59 | Q_DECLARE_PUBLIC(QGrpcClientBase) |
60 | public: |
61 | explicit QGrpcClientBasePrivate(QLatin1StringView service) : service(service) { } |
62 | |
63 | void addStream(QGrpcOperation *stream); |
64 | std::optional<QByteArray> trySerialize(const QProtobufMessage &arg) const; |
65 | bool isReady() const; |
66 | |
67 | std::shared_ptr<QAbstractProtobufSerializer> serializer() const |
68 | { |
69 | return channel ? channel->serializer() : nullptr; |
70 | } |
71 | |
72 | template <typename T> |
73 | [[nodiscard]] std::unique_ptr<T> initOperation(QLatin1StringView method, |
74 | const QProtobufMessage &arg, |
75 | const QGrpcCallOptions &options) |
76 | { |
77 | if (!isReady()) |
78 | return {}; |
79 | |
80 | const auto argData = trySerialize(arg); |
81 | if (!argData) |
82 | return {}; |
83 | |
84 | using ChannelFn = std::unique_ptr<T> (QAbstractGrpcChannel::*)(QLatin1StringView, |
85 | QLatin1StringView, |
86 | QByteArrayView, |
87 | const QGrpcCallOptions &); |
88 | constexpr ChannelFn initializer = [&]() -> ChannelFn { |
89 | if constexpr (std::is_same_v<T, QGrpcServerStream>) { |
90 | return &QAbstractGrpcChannel::serverStream; |
91 | } else if constexpr (std::is_same_v<T, QGrpcClientStream>) { |
92 | return &QAbstractGrpcChannel::clientStream; |
93 | } else if constexpr (std::is_same_v<T, QGrpcBidiStream>) { |
94 | return &QAbstractGrpcChannel::bidiStream; |
95 | } else if constexpr (std::is_same_v<T, QGrpcCallReply>) { |
96 | return &QAbstractGrpcChannel::call; |
97 | } else { |
98 | Q_UNREACHABLE_RETURN(nullptr); |
99 | } |
100 | }(); |
101 | |
102 | std::unique_ptr<T> operation = ((*channel).*(initializer))(method, service, *argData, |
103 | options); |
104 | |
105 | if constexpr (IsStream<T>) |
106 | addStream(stream: operation.get()); |
107 | return operation; |
108 | } |
109 | |
110 | std::shared_ptr<QAbstractGrpcChannel> channel; |
111 | const QLatin1StringView service; |
112 | QMinimalFlatSet<QGrpcOperation *> activeStreams; |
113 | }; |
114 | |
115 | void QGrpcClientBasePrivate::addStream(QGrpcOperation *grpcStream) |
116 | { |
117 | Q_ASSERT(grpcStream); |
118 | |
119 | Q_Q(QGrpcClientBase); |
120 | // Remove the operation pointer upon QObject destruction if it hasn't |
121 | // already been gracefully removed by receiving finished() |
122 | QObject::connect(sender: grpcStream, signal: &QObject::destroyed, context: q, slot: [this, grpcStream](QObject *obj) { |
123 | Q_ASSERT(obj == grpcStream); |
124 | activeStreams.remove(v: grpcStream); |
125 | }); |
126 | |
127 | auto finishedConnection = std::make_shared<QMetaObject::Connection>(); |
128 | *finishedConnection = QObject::connect(sender: grpcStream, signal: &QGrpcOperation::finished, context: q, |
129 | slot: [this, grpcStream, finishedConnection] { |
130 | Q_ASSERT(activeStreams.contains(grpcStream)); |
131 | activeStreams.remove(v: grpcStream); |
132 | QObject::disconnect(*finishedConnection); |
133 | }); |
134 | const auto it = activeStreams.insert(v: grpcStream); |
135 | Q_ASSERT(it.second); |
136 | } |
137 | |
138 | std::optional<QByteArray> QGrpcClientBasePrivate::trySerialize(const QProtobufMessage &arg) const |
139 | { |
140 | if (auto s = serializer()) |
141 | return s->serialize(message: &arg); |
142 | |
143 | qGrpcWarning("Serializing failed. Serializer is not ready" ); |
144 | return std::nullopt; |
145 | } |
146 | |
147 | bool QGrpcClientBasePrivate::isReady() const |
148 | { |
149 | Q_Q(const QGrpcClientBase); |
150 | if (q->thread() != QThread::currentThread()) { |
151 | qGrpcWarning("QtGrpc doesn't support invocation from a different thread" ); |
152 | return false; |
153 | } |
154 | |
155 | if (!channel) { |
156 | qGrpcWarning("No channel(s) attached" ); |
157 | return false; |
158 | } |
159 | return true; |
160 | } |
161 | |
162 | /*! |
163 | \internal |
164 | Constructs a QGrpcClientBase using \a service name from the protobuf schema |
165 | and sets \a parent as the owner. |
166 | */ |
167 | QGrpcClientBase::QGrpcClientBase(QLatin1StringView service, QObject *parent) |
168 | : QObject(*new QGrpcClientBasePrivate(service), parent) |
169 | { |
170 | } |
171 | |
172 | /*! |
173 | Destroys the QGrpcClientBase. |
174 | */ |
175 | QGrpcClientBase::~QGrpcClientBase() = default; |
176 | |
177 | /*! |
178 | Attaches \a channel to the client as transport layer for \gRPC operations. |
179 | Returns \c true if the channel successfully attached; otherwise, returns \c |
180 | false. |
181 | |
182 | Request and response messages will be serialized in a format that the |
183 | channel supports. |
184 | |
185 | \note \b Warning: Qt GRPC doesn't guarantee thread safety on the channel level. |
186 | You have to invoke the channel-related functions on the same thread as |
187 | QGrpcClientBase. |
188 | */ |
189 | bool QGrpcClientBase::attachChannel(std::shared_ptr<QAbstractGrpcChannel> channel) |
190 | { |
191 | Q_D(QGrpcClientBase); |
192 | // channel is not a QObject so we compare against the threadId set on construction. |
193 | if (channel->d_func()->threadId != QThread::currentThreadId()) { |
194 | qGrpcWarning("QtGrpc doesn't allow attaching the channel from a different thread" ); |
195 | return false; |
196 | } |
197 | |
198 | for (const auto &stream : d->activeStreams) { |
199 | assert(stream != nullptr); |
200 | stream->cancel(); |
201 | } |
202 | |
203 | d->channel = std::move(channel); |
204 | emit channelChanged(); |
205 | return true; |
206 | } |
207 | |
208 | /*! |
209 | \since 6.7 |
210 | Returns the channel attached to this client. |
211 | */ |
212 | std::shared_ptr<QAbstractGrpcChannel> QGrpcClientBase::channel() const |
213 | { |
214 | Q_D(const QGrpcClientBase); |
215 | return d->channel; |
216 | } |
217 | |
218 | /*! |
219 | \internal |
220 | //! [rpc-init-desc] |
221 | Initializes the RPC with \a method name and initial argument \a arg by |
222 | calling the corresponding QAbstractGrpcChannel method. The RPC is |
223 | customized through the provided \a options. |
224 | //! [rpc-init-desc] |
225 | */ |
226 | std::unique_ptr<QGrpcCallReply> QGrpcClientBase::call(QLatin1StringView method, |
227 | const QProtobufMessage &arg, |
228 | const QGrpcCallOptions &options) |
229 | { |
230 | Q_D(QGrpcClientBase); |
231 | return d->initOperation<QGrpcCallReply>(method, arg, options); |
232 | } |
233 | |
234 | /*! |
235 | \internal |
236 | \include qgrpcclientbase.cpp rpc-init-desc |
237 | */ |
238 | std::unique_ptr<QGrpcServerStream> QGrpcClientBase::serverStream(QLatin1StringView method, |
239 | const QProtobufMessage &arg, |
240 | const QGrpcCallOptions &options) |
241 | { |
242 | Q_D(QGrpcClientBase); |
243 | return d->initOperation<QGrpcServerStream>(method, arg, options); |
244 | } |
245 | |
246 | /*! |
247 | \internal |
248 | \include qgrpcclientbase.cpp rpc-init-desc |
249 | */ |
250 | std::unique_ptr<QGrpcClientStream> QGrpcClientBase::clientStream(QLatin1StringView method, |
251 | const QProtobufMessage &arg, |
252 | const QGrpcCallOptions &options) |
253 | { |
254 | Q_D(QGrpcClientBase); |
255 | return d->initOperation<QGrpcClientStream>(method, arg, options); |
256 | } |
257 | |
258 | /*! |
259 | \internal |
260 | \include qgrpcclientbase.cpp rpc-init-desc |
261 | */ |
262 | std::unique_ptr<QGrpcBidiStream> QGrpcClientBase::bidiStream(QLatin1StringView method, |
263 | const QProtobufMessage &arg, |
264 | const QGrpcCallOptions &options) |
265 | { |
266 | Q_D(QGrpcClientBase); |
267 | return d->initOperation<QGrpcBidiStream>(method, arg, options); |
268 | } |
269 | |
270 | bool QGrpcClientBase::event(QEvent *event) |
271 | { |
272 | return QObject::event(event); |
273 | } |
274 | |
275 | QT_END_NAMESPACE |
276 | |
277 | #include "moc_qgrpcclientbase.cpp" |
278 | |