OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_unix_inbound_socket.hpp
1#pragma once
2#include <ossia/network/context.hpp>
3#if defined(BOOST_ASIO_HAS_LOCAL_SOCKETS)
4#include <ossia/detail/variant.hpp>
5#include <ossia/network/sockets/configuration.hpp>
6#include <ossia/network/sockets/line_framing.hpp>
7#include <ossia/network/sockets/no_framing.hpp>
8#include <ossia/network/sockets/size_prefix_framing.hpp>
9#include <ossia/network/sockets/slip_framing.hpp>
10#include <ossia/network/sockets/unix_socket.hpp>
11
12#include <ossia-qt/protocols/utils.hpp>
13
14#include <QJSValue>
15#include <QObject>
16#include <QQmlEngine>
17
18#include <nano_observer.hpp>
19
20#include <verdigris>
21
22namespace ossia::qt
23{
24class qml_unix_datagram_inbound_socket
25 : public QObject
26 , public Nano::Observer
27{
28 W_OBJECT(qml_unix_datagram_inbound_socket)
29public:
30 struct state
31 {
32 ossia::net::unix_datagram_socket socket;
33 std::atomic_bool alive{true};
34
35 state(const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx)
36 : socket{conf, ctx}
37 {
38 }
39 };
40
41 qml_unix_datagram_inbound_socket() { }
42
43 ~qml_unix_datagram_inbound_socket()
44 {
45 if(m_state)
46 {
47 m_state->alive = false;
48 close();
49 }
50 }
51
52 bool isOpen() const noexcept { return m_state != nullptr; }
53
54 void open(const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx)
55 {
56 m_state = std::make_shared<state>(conf, ctx);
57
58 if(onClose.isCallable())
59 m_state->socket.on_close.connect<&qml_unix_datagram_inbound_socket::on_close>(this);
60
61 m_state->socket.open();
62 if(onOpen.isCallable())
63 onOpen.call({qjsEngine(this)->newQObject(this)});
64
65 auto st = m_state;
66 auto self = QPointer{this};
67 st->socket.receive([st, self](const char* data, std::size_t sz) {
68 if(!st->alive)
69 return;
70 ossia::qt::run_async(
71 self.get(),
72 [self, arg = QByteArray(data, sz)] {
73 if(!self.get())
74 return;
75 if(self->onMessage.isCallable())
76 {
77 self->onMessage.call({qjsEngine(self.get())->toScriptValue(arg)});
78 }
79 },
80 Qt::AutoConnection);
81 });
82 }
83
84 void close()
85 {
86 if(!m_state)
87 return;
88 if(!m_state->socket.m_socket.is_open())
89 return;
90 auto st = m_state;
91 boost::asio::post(st->socket.m_context, [st] {
92 try
93 {
94 st->socket.m_socket.shutdown(boost::asio::ip::udp::socket::shutdown_both);
95 }
96 catch(...)
97 {
98 }
99 st->socket.m_socket.close();
100 st->socket.on_close();
101 });
102 }
103 W_SLOT(close)
104
105 void on_close()
106 {
107 if(!m_state || !m_state->alive)
108 return;
109 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
110 }
111
112 QJSValue onOpen;
113 QJSValue onClose;
114 QJSValue onError;
115 QJSValue onMessage;
116
117private:
118 std::shared_ptr<state> m_state;
119};
120
121class qml_unix_stream_connection
122 : public QObject
123 , public Nano::Observer
124{
125 W_OBJECT(qml_unix_stream_connection)
126public:
127 using socket_t = boost::asio::local::stream_protocol::socket;
128 using decoder_type = ossia::slow_variant<
129 ossia::net::no_framing::decoder<socket_t>,
130 ossia::net::slip_decoder<socket_t>,
131 ossia::net::size_prefix_decoder<socket_t>,
132 ossia::net::line_framing_decoder<socket_t>>;
133
134 struct state
135 {
136 boost::asio::io_context& context;
137 ossia::net::unix_stream_listener listener;
138 std::atomic_bool alive{true};
139 ossia::net::framing framing{ossia::net::framing::none};
140 char line_delimiter[8] = {};
141 decoder_type decoder;
142
143 state(
144 ossia::net::unix_stream_listener l, boost::asio::io_context& ctx,
145 ossia::net::framing f = ossia::net::framing::none,
146 const std::string& delim = {})
147 : context{ctx}
148 , listener{std::move(l)}
149 , decoder{ossia::in_place_index<0>, listener.m_socket}
150 {
151 framing = f;
152 if(!delim.empty())
153 {
154 auto sz = std::min(delim.size(), (size_t)7);
155 std::copy_n(delim.begin(), sz, line_delimiter);
156 }
157 switch(f)
158 {
159 default:
160 case ossia::net::framing::none:
161 break;
162 case ossia::net::framing::slip:
163 decoder.template emplace<1>(listener.m_socket);
164 break;
165 case ossia::net::framing::size_prefix:
166 decoder.template emplace<2>(listener.m_socket);
167 break;
168 case ossia::net::framing::line_delimiter:
169 decoder.template emplace<3>(listener.m_socket);
170 {
171 auto& dec = ossia::get<3>(decoder);
172 std::copy_n(line_delimiter, 8, dec.delimiter);
173 }
174 break;
175 }
176 }
177
178 void write_encoded(const char* data, std::size_t sz)
179 {
180 switch(framing)
181 {
182 default:
183 case ossia::net::framing::none:
184 listener.write(boost::asio::const_buffer(data, sz));
185 break;
186 case ossia::net::framing::slip: {
187 ossia::net::slip_encoder<socket_t> enc{listener.m_socket};
188 enc.write(data, sz);
189 break;
190 }
191 case ossia::net::framing::size_prefix: {
192 ossia::net::size_prefix_encoder<socket_t> enc{listener.m_socket};
193 enc.write(data, sz);
194 break;
195 }
196 case ossia::net::framing::line_delimiter: {
197 ossia::net::line_framing_encoder<socket_t> enc{listener.m_socket};
198 std::copy_n(line_delimiter, 8, enc.delimiter);
199 enc.write(data, sz);
200 break;
201 }
202 }
203 }
204 };
205
206 struct receive_callback
207 {
208 std::shared_ptr<state> st;
209 QPointer<qml_unix_stream_connection> self;
210
211 void operator()(const unsigned char* data, std::size_t sz) const
212 {
213 if(!st->alive)
214 return;
215 auto buf = QByteArray((const char*)data, sz);
216 ossia::qt::run_async(
217 self.get(),
218 [self = self, buf] {
219 if(!self.get())
220 return;
221 if(self->onMessage.isCallable())
222 {
223 self->onMessage.call({qjsEngine(self.get())->toScriptValue(buf)});
224 }
225 },
226 Qt::AutoConnection);
227 }
228
229 bool validate_stream(boost::system::error_code ec) const
230 {
231 if(ec == boost::asio::error::operation_aborted)
232 return false;
233 if(ec == boost::asio::error::eof)
234 return false;
235 return true;
236 }
237 };
238
239 qml_unix_stream_connection(
240 ossia::net::unix_stream_listener listener, boost::asio::io_context& ctx,
241 ossia::net::framing f = ossia::net::framing::none,
242 const std::string& delim = {})
243 : m_state{std::make_shared<state>(std::move(listener), ctx, f, delim)}
244 {
245 }
246
247 ~qml_unix_stream_connection()
248 {
249 if(m_state)
250 {
251 m_state->alive = false;
252 close({});
253 }
254 }
255
256 bool isOpen() const noexcept { return m_state && m_state->alive; }
257
258 inline boost::asio::io_context& context() noexcept { return m_state->context; }
259
260 void write(QByteArray buffer)
261 {
262 if(!m_state)
263 return;
264 auto st = m_state;
265 boost::asio::dispatch(st->context, [st, buffer] {
266 if(st->alive)
267 st->write_encoded(buffer.data(), buffer.size());
268 });
269 }
270 W_SLOT(write)
271
272 void close(QByteArray)
273 {
274 if(!m_state)
275 return;
276 auto st = m_state;
277 boost::asio::dispatch(st->context, [st] { st->listener.close(); });
278 }
279 W_SLOT(close)
280
281 void startReceive()
282 {
283 auto st = m_state;
284 auto self = QPointer{this};
285 ossia::visit(
286 [cb = receive_callback{st, self}](auto& decoder) mutable {
287 decoder.receive(std::move(cb));
288 },
289 st->decoder);
290 }
291
292 QJSValue onMessage;
293
294private:
295 std::shared_ptr<state> m_state;
296};
297
298class qml_unix_stream_inbound_socket
299 : public QObject
300 , public Nano::Observer
301{
302 W_OBJECT(qml_unix_stream_inbound_socket)
303public:
304 struct state
305 {
306 ossia::net::unix_stream_server server;
307 std::atomic_bool alive{true};
308 ossia::net::framing framing{ossia::net::framing::none};
309 std::string framing_delimiter;
310
311 state(
312 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
313 ossia::net::framing f = ossia::net::framing::none,
314 std::string delim = {})
315 : server{conf, ctx}
316 , framing{f}
317 , framing_delimiter{std::move(delim)}
318 {
319 }
320 };
321
322 qml_unix_stream_inbound_socket() { }
323
324 ~qml_unix_stream_inbound_socket()
325 {
326 if(m_state)
327 {
328 m_state->alive = false;
329 close();
330 }
331 }
332
333 bool isOpen() const noexcept { return m_state != nullptr; }
334
335 void open(
336 const ossia::net::fd_configuration& conf, boost::asio::io_context& ctx,
337 ossia::net::framing f = ossia::net::framing::none,
338 const std::string& delim = {})
339 {
340 m_state = std::make_shared<state>(conf, ctx, f, delim);
341
342 accept_impl(m_state, QPointer{this});
343 if(onOpen.isCallable())
344 onOpen.call({qjsEngine(this)->newQObject(this)});
345 }
346
347 void close()
348 {
349 if(!m_state)
350 return;
351 m_state->server.m_acceptor.close();
352 if(onClose.isCallable())
353 onClose.call();
354 }
355 W_SLOT(close)
356
357 void on_close()
358 {
359 if(!m_state || !m_state->alive)
360 return;
361 ossia::qt::run_async(this, [=, this] { onClose.call(); }, Qt::AutoConnection);
362 }
363
364 QJSValue onOpen;
365 QJSValue onClose;
366 QJSValue onError;
367 QJSValue onConnection;
368
369private:
370 static void accept_impl(
371 std::shared_ptr<state> st, QPointer<qml_unix_stream_inbound_socket> self)
372 {
373 st->server.m_acceptor.async_accept(
374 [self, st](
375 boost::system::error_code ec,
376 ossia::net::unix_stream_server::proto::socket socket) {
377 if(!st->alive)
378 return;
379 if(!ec)
380 {
381 ossia::qt::run_async(
382 self.get(),
383 [self, st, socket = std::move(socket)]() mutable {
384 if(!self.get())
385 return;
386 auto conn = new qml_unix_stream_connection{
387 ossia::net::unix_stream_listener{std::move(socket)},
388 st->server.m_context, st->framing, st->framing_delimiter};
389 conn->onMessage = self->onConnection;
390 conn->startReceive();
391
392 if(self->onConnection.isCallable())
393 {
394 self->onConnection.call(
395 {qjsEngine(self.get())->newQObject(static_cast<QObject*>(conn))});
396 }
397 },
398 Qt::AutoConnection);
399 accept_impl(st, self);
400 }
401 });
402 }
403
404 std::shared_ptr<state> m_state;
405};
406}
407#endif
Definition qml_device.cpp:43
Definition git_info.h:7