samer@0
|
1 /*
|
samer@0
|
2 * Copyright (C) 2009 Samer Abdallah
|
samer@0
|
3 *
|
samer@0
|
4 * This program is free software; you can redistribute it and/or modify
|
samer@0
|
5 * it under the terms of the GNU General Public License as published by
|
samer@0
|
6 * the Free Software Foundation; either version 2 of the License, or
|
samer@0
|
7 * (at your option) any later version.
|
samer@0
|
8 *
|
samer@0
|
9 * This program is distributed in the hope that it will be useful,
|
samer@0
|
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
samer@0
|
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
samer@0
|
12 * GNU General Public License for more details.
|
samer@0
|
13 *
|
samer@0
|
14 */
|
samer@0
|
15
|
samer@0
|
16 #include <SWI-Stream.h>
|
samer@0
|
17 #include <SWI-Prolog.h>
|
samer@0
|
18
|
samer@0
|
19 #include <stdio.h>
|
samer@0
|
20 #include <string.h>
|
samer@0
|
21 #include <math.h>
|
samer@0
|
22 #include <lo/lo.h>
|
samer@0
|
23
|
samer@0
|
24 // ---------------------------------------------------------------------------
|
samer@0
|
25
|
samer@0
|
26 // Reimplementation of lo_server_thread to all calls to
|
samer@0
|
27 // Prolog from the server thread.
|
samer@0
|
28
|
samer@0
|
29 typedef struct _my_server_thread {
|
samer@0
|
30 lo_server s;
|
samer@0
|
31 pthread_t thread;
|
samer@0
|
32 volatile int active;
|
samer@0
|
33 volatile int done;
|
samer@0
|
34 } *my_server_thread;
|
samer@0
|
35
|
samer@0
|
36 int my_server_thread_start(my_server_thread st);
|
samer@0
|
37 int my_server_thread_stop(my_server_thread st);
|
samer@0
|
38 int my_server_thread_run(my_server_thread st, int timeout);
|
samer@0
|
39 void my_server_thread_free(my_server_thread st);
|
samer@0
|
40 my_server_thread my_server_thread_new(const char *port, lo_err_handler err_h);
|
samer@0
|
41
|
samer@0
|
42 // ---------------------------------------------------------------------------
|
samer@0
|
43
|
samer@0
|
44 // BLOB to hold a lo_address
|
samer@0
|
45 static PL_blob_t addr_blob;
|
samer@0
|
46
|
samer@0
|
47 // BLOB to hold server thread
|
samer@0
|
48 static PL_blob_t server_blob;
|
samer@0
|
49
|
samer@0
|
50 static predicate_t call3, call5;
|
samer@0
|
51 static atom_t osc_immed;
|
samer@0
|
52 static functor_t osc_ts_2;
|
samer@0
|
53 static functor_t int_1, float_1, double_1, string_1;
|
samer@0
|
54
|
samer@0
|
55 install_t install();
|
samer@0
|
56
|
samer@0
|
57 foreign_t mk_address( term_t host, term_t port, term_t addr);
|
samer@0
|
58 foreign_t is_address( term_t addr);
|
samer@0
|
59 foreign_t send_osc_now( term_t addr, term_t msg, term_t args);
|
samer@0
|
60 foreign_t send_osc_at( term_t addr, term_t msg, term_t args, term_t time);
|
samer@0
|
61 foreign_t send_osc_from_at( term_t serv, term_t addr, term_t msg, term_t args, term_t time);
|
samer@0
|
62 foreign_t send_timestamped( term_t addr, term_t msg, term_t args, term_t sec, term_t frac);
|
samer@0
|
63 foreign_t now( term_t sec, term_t frac);
|
samer@0
|
64 foreign_t time_to_ts( term_t time, term_t sec, term_t frac);
|
samer@0
|
65 foreign_t time_from_ts( term_t time, term_t sec, term_t frac);
|
samer@0
|
66
|
samer@0
|
67 // OSC server predicates
|
samer@0
|
68 foreign_t mk_server( term_t port, term_t server);
|
samer@0
|
69 foreign_t start_server( term_t server);
|
samer@0
|
70 foreign_t stop_server( term_t server);
|
samer@0
|
71 foreign_t del_handler( term_t server, term_t msg, term_t types);
|
samer@0
|
72 foreign_t add_handler( term_t server, term_t msg, term_t types, term_t handler);
|
samer@0
|
73 foreign_t add_handler_x( term_t server, term_t msg, term_t types, term_t handler);
|
samer@0
|
74 foreign_t run_server( term_t server);
|
samer@0
|
75
|
samer@0
|
76
|
samer@0
|
77 // BLOB functions
|
samer@0
|
78 int addr_release(atom_t a) {
|
samer@0
|
79 PL_blob_t *type;
|
samer@0
|
80 size_t len;
|
samer@0
|
81 void *p=PL_blob_data(a,&len,&type);
|
samer@0
|
82 if (p) lo_address_free(*(lo_address *)p);
|
samer@0
|
83 return TRUE;
|
samer@0
|
84 }
|
samer@0
|
85
|
samer@0
|
86 int addr_write(IOSTREAM *s, atom_t a, int flags) {
|
samer@0
|
87 PL_blob_t *type;
|
samer@0
|
88 size_t len;
|
samer@0
|
89 lo_address *p=(lo_address *)PL_blob_data(a,&len,&type);
|
samer@0
|
90 if (p) {
|
samer@0
|
91 const char *host = lo_address_get_hostname(*p);
|
samer@0
|
92 const char *port = lo_address_get_port(*p);
|
samer@0
|
93 if (host!=NULL && port!=NULL) {
|
samer@0
|
94 Sfprintf(s,"osc_address<%s:%s>",host,port);
|
samer@0
|
95 } else {
|
samer@0
|
96 Sfprintf(s,"osc_address<invalid>");
|
samer@0
|
97 }
|
samer@0
|
98 }
|
samer@0
|
99 return TRUE;
|
samer@0
|
100 }
|
samer@0
|
101
|
samer@0
|
102 int server_release(atom_t a) {
|
samer@0
|
103 PL_blob_t *type;
|
samer@0
|
104 size_t len;
|
samer@0
|
105 void *p=PL_blob_data(a,&len,&type);
|
samer@0
|
106 if (p) my_server_thread_free(*(my_server_thread *)p);
|
samer@0
|
107 return TRUE;
|
samer@0
|
108 }
|
samer@0
|
109
|
samer@0
|
110 int server_write(IOSTREAM *s, atom_t a, int flags) {
|
samer@0
|
111 PL_blob_t *type;
|
samer@0
|
112 size_t len;
|
samer@0
|
113 my_server_thread *p=(my_server_thread *)PL_blob_data(a,&len,&type);
|
samer@0
|
114 if (p) {
|
samer@0
|
115 char *url=lo_server_get_url((*p)->s);
|
samer@0
|
116 Sfprintf(s,"osc_server<%s>",url);
|
samer@0
|
117 free(url);
|
samer@0
|
118 }
|
samer@0
|
119 return TRUE;
|
samer@0
|
120 }
|
samer@0
|
121
|
samer@0
|
122 install_t install() {
|
samer@0
|
123 PL_register_foreign("osc_now", 2, (void *)now, 0);
|
samer@0
|
124 PL_register_foreign("time_to_ts", 3, (void *)time_to_ts, 0);
|
samer@0
|
125 PL_register_foreign("time_from_ts", 3, (void *)time_from_ts, 0);
|
samer@0
|
126 PL_register_foreign("osc_mk_address", 3, (void *)mk_address, 0);
|
samer@0
|
127 PL_register_foreign("osc_is_address", 1, (void *)is_address, 0);
|
samer@0
|
128 PL_register_foreign("osc_send_now", 3, (void *)send_osc_now, 0);
|
samer@0
|
129 PL_register_foreign("osc_send_at", 4, (void *)send_osc_at, 0);
|
samer@0
|
130 PL_register_foreign("osc_send_from_at", 5, (void *)send_osc_from_at, 0);
|
samer@0
|
131 PL_register_foreign("osc_mk_server", 2, (void *)mk_server, 0);
|
samer@0
|
132 PL_register_foreign("osc_start_server", 1, (void *)start_server, 0);
|
samer@0
|
133 PL_register_foreign("osc_stop_server", 1, (void *)stop_server, 0);
|
samer@0
|
134 PL_register_foreign("osc_run_server", 1, (void *)run_server, 0);
|
samer@0
|
135 PL_register_foreign("osc_del_method", 3, (void *)del_handler, 0);
|
samer@0
|
136 PL_register_foreign("osc_add_method", 4, (void *)add_handler, 0);
|
samer@0
|
137 PL_register_foreign("osc_add_method_x", 4, (void *)add_handler_x, 0);
|
samer@0
|
138
|
samer@0
|
139 addr_blob.magic = PL_BLOB_MAGIC;
|
samer@0
|
140 addr_blob.flags = PL_BLOB_UNIQUE;
|
samer@0
|
141 addr_blob.name = "osc_address";
|
samer@0
|
142 addr_blob.acquire = 0;
|
samer@0
|
143 addr_blob.release = addr_release;
|
samer@0
|
144 addr_blob.write = addr_write;
|
samer@0
|
145 addr_blob.compare = 0;
|
samer@0
|
146
|
samer@0
|
147 server_blob.magic = PL_BLOB_MAGIC;
|
samer@0
|
148 server_blob.flags = PL_BLOB_UNIQUE;
|
samer@0
|
149 server_blob.name = "osc_server";
|
samer@0
|
150 server_blob.acquire = 0;
|
samer@0
|
151 server_blob.release = server_release;
|
samer@0
|
152 server_blob.write = server_write;
|
samer@0
|
153 server_blob.compare = 0;
|
samer@0
|
154
|
samer@0
|
155 call3 = PL_predicate("call",3,"user");
|
samer@0
|
156 call5 = PL_predicate("call",5,"user");
|
samer@0
|
157 osc_immed = PL_new_atom("osc_immed");
|
samer@0
|
158 osc_ts_2 = PL_new_functor(PL_new_atom("osc_ts"),2);
|
samer@0
|
159 int_1 = PL_new_functor(PL_new_atom("int"),1);
|
samer@0
|
160 float_1 = PL_new_functor(PL_new_atom("float"),1);
|
samer@0
|
161 double_1 = PL_new_functor(PL_new_atom("double"),1);
|
samer@0
|
162 string_1 = PL_new_functor(PL_new_atom("string"),1);
|
samer@0
|
163 }
|
samer@0
|
164
|
samer@0
|
165 // throws a Prolog exception to signal type error
|
samer@0
|
166 static int type_error(term_t actual, const char *expected)
|
samer@0
|
167 {
|
samer@0
|
168 term_t ex = PL_new_term_ref();
|
samer@0
|
169 int rc;
|
samer@0
|
170
|
samer@0
|
171 rc = PL_unify_term(ex, PL_FUNCTOR_CHARS, "error", 2,
|
samer@0
|
172 PL_FUNCTOR_CHARS, "type_error", 2,
|
samer@0
|
173 PL_CHARS, expected,
|
samer@0
|
174 PL_TERM, actual,
|
samer@0
|
175 PL_VARIABLE);
|
samer@0
|
176
|
samer@0
|
177 return PL_raise_exception(ex);
|
samer@0
|
178 }
|
samer@0
|
179
|
samer@0
|
180 static int osc_error(int errno, const char *errmsg, const char *msg)
|
samer@0
|
181 {
|
samer@0
|
182 term_t ex = PL_new_term_ref();
|
samer@0
|
183 int rc;
|
samer@0
|
184
|
samer@0
|
185 rc=PL_unify_term(ex, PL_FUNCTOR_CHARS, "error", 1,
|
samer@0
|
186 PL_FUNCTOR_CHARS, "osc_error", 3,
|
samer@0
|
187 PL_INTEGER, errno,
|
samer@0
|
188 PL_CHARS, errmsg,
|
samer@0
|
189 PL_CHARS, msg==NULL ? "none" : msg);
|
samer@0
|
190
|
samer@0
|
191 return PL_raise_exception(ex);
|
samer@0
|
192 }
|
samer@0
|
193
|
samer@0
|
194 static int arg_error(const char *type, term_t arg)
|
samer@0
|
195 {
|
samer@0
|
196 term_t ex = PL_new_term_ref();
|
samer@0
|
197 int rc;
|
samer@0
|
198
|
samer@0
|
199 rc=PL_unify_term(ex, PL_FUNCTOR_CHARS, "error", 1,
|
samer@0
|
200 PL_FUNCTOR_CHARS, "arg_error", 2,
|
samer@0
|
201 PL_CHARS, type,
|
samer@0
|
202 PL_TERM, arg);
|
samer@0
|
203
|
samer@0
|
204 return PL_raise_exception(ex);
|
samer@0
|
205 }
|
samer@0
|
206
|
samer@0
|
207 // put lo_address into a Prolog BLOB
|
samer@0
|
208 static int unify_addr(term_t addr,lo_address a) {
|
samer@0
|
209 return PL_unify_blob(addr, &a, sizeof(lo_address), &addr_blob);
|
samer@0
|
210 }
|
samer@0
|
211
|
samer@0
|
212 // get lo_address from BLOB
|
samer@0
|
213 static int get_addr(term_t addr, lo_address *a)
|
samer@0
|
214 {
|
samer@0
|
215 PL_blob_t *type;
|
samer@0
|
216 size_t len;
|
samer@0
|
217 lo_address *a1;
|
samer@0
|
218
|
samer@0
|
219 PL_get_blob(addr, (void **)&a1, &len, &type);
|
samer@0
|
220 if (type != &addr_blob) {
|
samer@0
|
221 return type_error(addr, "osc_address");
|
samer@0
|
222 } else {
|
samer@0
|
223 *a=*a1;
|
samer@0
|
224 return TRUE;
|
samer@0
|
225 }
|
samer@0
|
226 }
|
samer@0
|
227
|
samer@0
|
228 // put lo_address into a Prolog BLOB
|
samer@0
|
229 static int unify_server(term_t server,my_server_thread s) {
|
samer@0
|
230 return PL_unify_blob(server, &s, sizeof(my_server_thread), &server_blob);
|
samer@0
|
231 }
|
samer@0
|
232
|
samer@0
|
233 // get my_server_thread from BLOB
|
samer@0
|
234 static int get_server(term_t server, my_server_thread *a)
|
samer@0
|
235 {
|
samer@0
|
236 PL_blob_t *type;
|
samer@0
|
237 size_t len;
|
samer@0
|
238 my_server_thread *a1;
|
samer@0
|
239
|
samer@0
|
240 PL_get_blob(server, (void **)&a1, &len, &type);
|
samer@0
|
241 if (type != &server_blob) {
|
samer@0
|
242 return type_error(server, "osc_server");
|
samer@0
|
243 } else {
|
samer@0
|
244 *a=*a1;
|
samer@0
|
245 return TRUE;
|
samer@0
|
246 }
|
samer@0
|
247 }
|
samer@0
|
248
|
samer@0
|
249 // get Prolog (Unix) time value and convert to OSC timestamp
|
samer@0
|
250 static int get_prolog_time(term_t time, lo_timetag *ts) {
|
samer@0
|
251 double t, ft;
|
samer@0
|
252 int ok = PL_get_float(time, &t);
|
samer@0
|
253
|
samer@0
|
254 ft=floor(t);
|
samer@0
|
255 ts->sec = ((uint32_t)ft)+2208988800u;
|
samer@0
|
256 ts->frac = (uint32_t)(4294967296.0*(t-ft));
|
samer@0
|
257 return ok;
|
samer@0
|
258 }
|
samer@0
|
259
|
samer@0
|
260 static int get_timetag(term_t sec, term_t frac, lo_timetag *ts) {
|
samer@0
|
261 int64_t s, f;
|
samer@0
|
262 int ok = PL_get_int64(sec, &s) && PL_get_int64(frac, &f);
|
samer@0
|
263 ts->sec = s;
|
samer@0
|
264 ts->frac = f;
|
samer@0
|
265 return ok;
|
samer@0
|
266 }
|
samer@0
|
267
|
samer@0
|
268
|
samer@0
|
269 static int get_msg(term_t msg, char **m) {
|
samer@0
|
270 int rc=PL_get_chars(msg, m, CVT_ATOM | CVT_STRING);
|
samer@0
|
271 if (rc && strcmp(*m,"any")==0) *m=NULL;
|
samer@0
|
272 return rc;
|
samer@0
|
273 }
|
samer@0
|
274
|
samer@0
|
275 // parse a list of Prolog terms and add arguments to an OSC message
|
samer@0
|
276 static int add_msg_args(lo_message msg, term_t list)
|
samer@0
|
277 {
|
samer@0
|
278 term_t head=PL_new_term_ref();
|
samer@0
|
279
|
samer@0
|
280 // copy term ref so as not to modify original
|
samer@0
|
281 list=PL_copy_term_ref(list);
|
samer@0
|
282
|
samer@0
|
283 while (PL_get_list(list,head,list)) {
|
samer@0
|
284 atom_t name;
|
samer@0
|
285 int rc, arity;
|
samer@0
|
286 const char *type;
|
samer@0
|
287
|
samer@0
|
288 if (!PL_get_name_arity(head,&name,&arity)) return type_error(head,"term");
|
samer@0
|
289 type=PL_atom_chars(name);
|
samer@0
|
290 switch (arity) {
|
samer@0
|
291 case 1: {
|
samer@0
|
292 term_t a1=PL_new_term_ref();
|
samer@0
|
293 rc=PL_get_arg(1,head,a1); // !!!! check return value
|
samer@0
|
294
|
samer@0
|
295 if (!strcmp(type,"int")) {
|
samer@0
|
296 int x;
|
samer@0
|
297 if (!PL_get_integer(a1,&x)) return type_error(a1,"integer");
|
samer@0
|
298 lo_message_add_int32(msg,x);
|
samer@0
|
299 } else if (!strcmp(type,"double")) {
|
samer@0
|
300 double x;
|
samer@0
|
301 if (!PL_get_float(a1,&x)) return type_error(a1,"float");
|
samer@0
|
302 lo_message_add_double(msg,x);
|
samer@0
|
303 } else if (!strcmp(type,"string")) {
|
samer@0
|
304 char *x;
|
samer@0
|
305 if (!PL_get_chars(a1,&x,CVT_ATOM|CVT_STRING)) return type_error(a1,"string");
|
samer@0
|
306 lo_message_add_string(msg,x);
|
samer@0
|
307 } else if (!strcmp(type,"symbol")) {
|
samer@0
|
308 char *x;
|
samer@0
|
309 if (!PL_get_chars(a1,&x,CVT_ATOM)) return type_error(a1,"atom");
|
samer@0
|
310 lo_message_add_symbol(msg,x);
|
samer@0
|
311 } else if (!strcmp(type,"float")) {
|
samer@0
|
312 double x;
|
samer@0
|
313 if (!PL_get_float(a1,&x)) return type_error(a1,"float");
|
samer@0
|
314 lo_message_add_float(msg,(float)x);
|
samer@0
|
315 } else {
|
samer@0
|
316 return arg_error(type,head);
|
samer@0
|
317 }
|
samer@0
|
318
|
samer@0
|
319 break;
|
samer@0
|
320 }
|
samer@0
|
321 case 0: {
|
samer@0
|
322 if (!strcmp(type,"true")) lo_message_add_true(msg);
|
samer@0
|
323 else if (!strcmp(type,"false")) lo_message_add_false(msg);
|
samer@0
|
324 else if (!strcmp(type,"nil")) lo_message_add_nil(msg);
|
samer@0
|
325 else if (!strcmp(type,"inf")) lo_message_add_infinitum(msg);
|
samer@0
|
326 break;
|
samer@0
|
327 }
|
samer@0
|
328 }
|
samer@0
|
329 }
|
samer@0
|
330 if (!PL_get_nil(list)) return type_error(list,"nil");
|
samer@0
|
331 return TRUE;
|
samer@0
|
332 }
|
samer@0
|
333
|
samer@0
|
334 static int send_msg_timestamped(lo_address a, lo_timetag *ts, char *path, term_t args)
|
samer@0
|
335 {
|
samer@0
|
336 lo_message msg=lo_message_new();
|
samer@0
|
337 lo_bundle bun=lo_bundle_new(*ts);
|
samer@0
|
338
|
samer@0
|
339 if (add_msg_args(msg,args)) {
|
samer@0
|
340 int ret;
|
samer@0
|
341
|
samer@0
|
342 lo_bundle_add_message(bun,path,msg);
|
samer@0
|
343 ret = lo_send_bundle(a,bun);
|
samer@0
|
344 lo_message_free(msg);
|
samer@0
|
345 lo_bundle_free(bun);
|
samer@0
|
346 if (ret==-1) {
|
samer@0
|
347 return osc_error(lo_address_errno(a),lo_address_errstr(a),path);
|
samer@0
|
348 } else {
|
samer@0
|
349 return TRUE;
|
samer@0
|
350 }
|
samer@0
|
351 } else return FALSE;
|
samer@0
|
352 }
|
samer@0
|
353
|
samer@0
|
354 static int send_msg_timestamped_from(lo_address a, lo_server s, lo_timetag *ts, char *path, term_t args)
|
samer@0
|
355 {
|
samer@0
|
356 lo_message msg=lo_message_new();
|
samer@0
|
357 lo_bundle bun=lo_bundle_new(*ts);
|
samer@0
|
358
|
samer@0
|
359 if (add_msg_args(msg,args)) {
|
samer@0
|
360 int ret;
|
samer@0
|
361
|
samer@0
|
362 lo_bundle_add_message(bun,path,msg);
|
samer@0
|
363 ret = lo_send_bundle_from(a,s,bun);
|
samer@0
|
364 lo_message_free(msg);
|
samer@0
|
365 lo_bundle_free(bun);
|
samer@0
|
366 if (ret==-1) {
|
samer@0
|
367 return osc_error(lo_address_errno(a),lo_address_errstr(a),path);
|
samer@0
|
368 } else {
|
samer@0
|
369 return TRUE;
|
samer@0
|
370 }
|
samer@0
|
371 } else return FALSE;
|
samer@0
|
372 }
|
samer@0
|
373
|
samer@0
|
374 static int send_msg(lo_address a, char *path, term_t args)
|
samer@0
|
375 {
|
samer@0
|
376 lo_message msg=lo_message_new();
|
samer@0
|
377
|
samer@0
|
378 if (add_msg_args(msg,args)) {
|
samer@0
|
379 if (lo_send_message(a,path,msg)==-1) {
|
samer@0
|
380 lo_message_free(msg);
|
samer@0
|
381 return osc_error(lo_address_errno(a),lo_address_errstr(a),path);
|
samer@0
|
382 } else {
|
samer@0
|
383 lo_message_free(msg);
|
samer@0
|
384 return TRUE;
|
samer@0
|
385 }
|
samer@0
|
386 } else return FALSE;
|
samer@0
|
387 }
|
samer@0
|
388
|
samer@0
|
389 foreign_t mk_address(term_t host, term_t port, term_t addr) {
|
samer@0
|
390 char *h, *p;
|
samer@0
|
391
|
samer@0
|
392 if (PL_get_chars(host, &h, CVT_ATOM | CVT_STRING)) {
|
samer@0
|
393 if (PL_get_chars(port, &p, CVT_INTEGER)) {
|
samer@0
|
394 lo_address a = lo_address_new(h,p);
|
samer@0
|
395 return unify_addr(addr,a);
|
samer@0
|
396 } else {
|
samer@0
|
397 return type_error(port,"integer");
|
samer@0
|
398 }
|
samer@0
|
399 } else {
|
samer@0
|
400 return type_error(host,"atom");
|
samer@0
|
401 }
|
samer@0
|
402 }
|
samer@0
|
403
|
samer@0
|
404 foreign_t now(term_t sec, term_t frac) {
|
samer@0
|
405 lo_timetag ts;
|
samer@0
|
406 int64_t s, f;
|
samer@0
|
407
|
samer@0
|
408 lo_timetag_now(&ts);
|
samer@0
|
409 s=ts.sec; f=ts.frac;
|
samer@0
|
410 return PL_unify_int64(sec,s) && PL_unify_int64(frac,f);
|
samer@0
|
411 }
|
samer@0
|
412
|
samer@0
|
413 foreign_t time_to_ts(term_t time, term_t sec, term_t frac) {
|
samer@0
|
414 lo_timetag ts;
|
samer@0
|
415
|
samer@0
|
416 return get_prolog_time(time,&ts) &&
|
samer@0
|
417 PL_unify_int64(sec,ts.sec) &&
|
samer@0
|
418 PL_unify_int64(frac,ts.frac);
|
samer@0
|
419 }
|
samer@0
|
420
|
samer@0
|
421 foreign_t time_from_ts(term_t time, term_t sec, term_t frac) {
|
samer@0
|
422 lo_timetag ts;
|
samer@0
|
423
|
samer@0
|
424 return get_timetag(sec,frac,&ts) &&
|
samer@0
|
425 PL_unify_float(time, (double)(ts.sec-2208988800u) + ts.frac/4294967296.0);
|
samer@0
|
426 }
|
samer@0
|
427
|
samer@0
|
428
|
samer@0
|
429
|
samer@0
|
430 // set current random state structure to values in Prolog term
|
samer@0
|
431 foreign_t is_address(term_t addr) {
|
samer@0
|
432 PL_blob_t *type;
|
samer@0
|
433 return PL_is_blob(addr,&type) && type==&addr_blob;
|
samer@0
|
434 }
|
samer@0
|
435
|
samer@0
|
436 foreign_t send_osc_from_at(term_t serv, term_t addr, term_t msg, term_t args, term_t time) {
|
samer@0
|
437 my_server_thread s;
|
samer@0
|
438 lo_address a;
|
samer@0
|
439 lo_timetag ts;
|
samer@0
|
440 char *m;
|
samer@0
|
441
|
samer@0
|
442 return get_addr(addr,&a) &&
|
samer@0
|
443 get_server(serv,&s) &&
|
samer@0
|
444 get_prolog_time(time,&ts) &&
|
samer@0
|
445 get_msg(msg, &m) &&
|
samer@0
|
446 send_msg_timestamped_from(a,s->s,&ts,m,args);
|
samer@0
|
447 }
|
samer@0
|
448
|
samer@0
|
449 foreign_t send_osc_at(term_t addr, term_t msg, term_t args, term_t time) {
|
samer@0
|
450 lo_address a;
|
samer@0
|
451 lo_timetag ts;
|
samer@0
|
452 char *m;
|
samer@0
|
453
|
samer@0
|
454 return get_addr(addr,&a) &&
|
samer@0
|
455 get_prolog_time(time,&ts) &&
|
samer@0
|
456 get_msg(msg, &m) &&
|
samer@0
|
457 send_msg_timestamped(a,&ts,m,args);
|
samer@0
|
458 }
|
samer@0
|
459
|
samer@0
|
460 foreign_t send_timestamped(term_t addr, term_t msg, term_t args, term_t secs, term_t frac) {
|
samer@0
|
461 lo_address a;
|
samer@0
|
462 lo_timetag ts;
|
samer@0
|
463 char *m;
|
samer@0
|
464
|
samer@0
|
465 return get_addr(addr,&a) &&
|
samer@0
|
466 get_timetag(secs,frac,&ts) &&
|
samer@0
|
467 get_msg(msg, &m) &&
|
samer@0
|
468 send_msg_timestamped(a,&ts,m,args);
|
samer@0
|
469 }
|
samer@0
|
470
|
samer@0
|
471
|
samer@0
|
472
|
samer@0
|
473 foreign_t send_osc_now(term_t addr, term_t msg, term_t args) {
|
samer@0
|
474 lo_address a;
|
samer@0
|
475 char *m;
|
samer@0
|
476
|
samer@0
|
477 return get_addr(addr,&a) &&
|
samer@0
|
478 get_msg(msg, &m) &&
|
samer@0
|
479 send_msg(a,m,args);
|
samer@0
|
480 }
|
samer@0
|
481
|
samer@0
|
482
|
samer@0
|
483
|
samer@0
|
484 /*
|
samer@0
|
485 * Server Bits
|
samer@0
|
486 */
|
samer@0
|
487
|
samer@0
|
488 static void prolog_thread_func(void *data);
|
samer@0
|
489
|
samer@0
|
490 // parse a list of type terms and encode as a NULL terminated
|
samer@0
|
491 // string where each character encodes the type of one argument.
|
samer@0
|
492 static int get_types_list(term_t list, char *typespec, int len)
|
samer@0
|
493 {
|
samer@0
|
494 term_t head=PL_new_term_ref();
|
samer@0
|
495 int count=0;
|
samer@0
|
496
|
samer@0
|
497 // copy term ref so as not to modify original
|
samer@0
|
498 list=PL_copy_term_ref(list);
|
samer@0
|
499
|
samer@0
|
500 while (PL_get_list(list,head,list) && count<len) {
|
samer@0
|
501 atom_t name;
|
samer@0
|
502 int arity;
|
samer@0
|
503 const char *type;
|
samer@0
|
504
|
samer@0
|
505 if (!PL_get_name_arity(head,&name,&arity)) return type_error(head,"term");
|
samer@0
|
506 type=PL_atom_chars(name);
|
samer@0
|
507 switch (arity) {
|
samer@0
|
508 case 1: {
|
samer@0
|
509 if (!strcmp(type,"int")) {
|
samer@0
|
510 typespec[count++]='i';
|
samer@0
|
511 } else if (!strcmp(type,"double")) {
|
samer@0
|
512 typespec[count++]='d';
|
samer@0
|
513 } else if (!strcmp(type,"string")) {
|
samer@0
|
514 typespec[count++]='s';
|
samer@0
|
515 } else if (!strcmp(type,"symbol")) {
|
samer@0
|
516 typespec[count++]='S';
|
samer@0
|
517 } else if (!strcmp(type,"float")) {
|
samer@0
|
518 typespec[count++]='f';
|
samer@0
|
519 }
|
samer@0
|
520 break;
|
samer@0
|
521 }
|
samer@0
|
522 case 0: {
|
samer@0
|
523 if (!strcmp(type,"true")) typespec[count++]='T';
|
samer@0
|
524 else if (!strcmp(type,"false")) typespec[count++]='F';
|
samer@0
|
525 else if (!strcmp(type,"nil")) typespec[count++]='N';
|
samer@0
|
526 else if (!strcmp(type,"inf")) typespec[count++]='I';
|
samer@0
|
527 break;
|
samer@0
|
528 }
|
samer@0
|
529 }
|
samer@0
|
530 }
|
samer@0
|
531 typespec[count]=0;
|
samer@0
|
532 if (!PL_get_nil(list)) return type_error(list,"nil");
|
samer@0
|
533 return TRUE;
|
samer@0
|
534 }
|
samer@0
|
535
|
samer@0
|
536 // parse a term representing argument types - types can be a list
|
samer@0
|
537 // as accepted by get_types_list() above or the atom 'any'
|
samer@0
|
538 static int get_types(term_t types, char *buffer, int len, char **typespec)
|
samer@0
|
539 {
|
samer@0
|
540 if (PL_is_list(types)) {
|
samer@0
|
541 *typespec=buffer;
|
samer@0
|
542 return get_types_list(types,buffer,len);
|
samer@0
|
543 } else if (PL_is_atom(types)) {
|
samer@0
|
544 char *a;
|
samer@0
|
545 if (PL_get_atom_chars(types,&a) && strcmp(a,"any")==0) {
|
samer@0
|
546 *typespec=NULL; return TRUE;
|
samer@0
|
547 } else return type_error(types,"list or 'any'");
|
samer@0
|
548 } else return type_error(types,"list or 'any'");
|
samer@0
|
549 }
|
samer@0
|
550
|
samer@0
|
551 // handler server error
|
samer@0
|
552 static void server_error(int num, const char *msg, const char *path) {
|
samer@0
|
553 osc_error(num,msg,path);
|
samer@0
|
554 }
|
samer@0
|
555
|
samer@0
|
556 // handle the /plosc/stop message for the synchronous server loop
|
samer@0
|
557 // in run_stoppable_server() and hence osc_run_server/1
|
samer@0
|
558 static int stop_handler(const char *path, const char *types, lo_arg **argv,
|
samer@0
|
559 int argc, lo_message msg, void *user_data)
|
samer@0
|
560 {
|
samer@0
|
561 my_server_thread s=(my_server_thread)user_data;
|
samer@0
|
562 s->active=0;
|
samer@0
|
563 return 1;
|
samer@0
|
564 }
|
samer@0
|
565
|
samer@0
|
566 // get message arguments and unify given term with list of arg terms
|
samer@0
|
567 static int unify_msg_args(term_t list, const char *types, lo_arg **argv, int argc)
|
samer@0
|
568 {
|
samer@0
|
569 int i, rc=0;
|
samer@0
|
570 for (i=0; i<argc; i++) {
|
samer@0
|
571 term_t head=PL_new_term_ref();
|
samer@0
|
572 term_t tail=PL_new_term_ref();
|
samer@0
|
573 if (!PL_unify_list(list,head,tail)) PL_fail;
|
samer@0
|
574 switch (types[i]) {
|
samer@0
|
575 case 'i': rc=PL_unify_term(head,PL_FUNCTOR, int_1, PL_INT,argv[i]->i); break;
|
samer@0
|
576 case 'f': rc=PL_unify_term(head,PL_FUNCTOR, float_1, PL_FLOAT,(double)argv[i]->f); break;
|
samer@0
|
577 case 'd': rc=PL_unify_term(head,PL_FUNCTOR, double_1, PL_DOUBLE,argv[i]->d); break;
|
samer@0
|
578 case 's': rc=PL_unify_term(head,PL_FUNCTOR, string_1, PL_CHARS,&argv[i]->s); break;
|
samer@0
|
579 case 'h': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"int64",1,PL_INT64,argv[i]->h); break;
|
samer@0
|
580 case 'c': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"char",1,PL_INT,(int)argv[i]->c); break;
|
samer@0
|
581 case 'S': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"symbol",1,PL_CHARS,&argv[i]->S); break;
|
samer@0
|
582 case 'T': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"true",0); break;
|
samer@0
|
583 case 'F': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"false",0); break;
|
samer@0
|
584 case 'N': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"nil",0); break;
|
samer@0
|
585 case 'I': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"inf",0); break;
|
samer@0
|
586 case 'b': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"blob",0); break;
|
samer@0
|
587 case 't': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"timetag",2,
|
samer@0
|
588 PL_INT64,(int64_t)argv[i]->t.sec,
|
samer@0
|
589 PL_INT64,(int64_t)argv[i]->t.frac);
|
samer@0
|
590 break;
|
samer@0
|
591 case 'm': rc=PL_unify_term(head,PL_FUNCTOR_CHARS,"midi",4,
|
samer@0
|
592 PL_INT,(int)argv[i]->m[0], PL_INT,(int)argv[i]->m[1],
|
samer@0
|
593 PL_INT,(int)argv[i]->m[2], PL_INT,(int)argv[i]->m[3]);
|
samer@0
|
594 break;
|
samer@0
|
595 }
|
samer@0
|
596 if (!rc) PL_fail;
|
samer@0
|
597 list=tail;
|
samer@0
|
598 }
|
samer@0
|
599 return PL_unify_nil(list);
|
samer@0
|
600 }
|
samer@0
|
601
|
samer@0
|
602 // handle OSC message by calling the associated Prolog goal
|
samer@0
|
603 static int prolog_handler(const char *path, const char *types, lo_arg **argv,
|
samer@0
|
604 int argc, lo_message msg, void *user_data)
|
samer@0
|
605 {
|
samer@0
|
606 term_t goal = PL_new_term_ref();
|
samer@0
|
607 term_t term0 = PL_new_term_refs(3);
|
samer@0
|
608
|
samer@0
|
609
|
samer@0
|
610 PL_recorded((record_t)user_data,goal); // retrieve the goal term
|
samer@0
|
611 PL_put_term(term0,goal); // term_t goal encoded in user_data
|
samer@0
|
612 PL_put_atom_chars(term0+1,path);
|
samer@0
|
613
|
samer@0
|
614 return !( unify_msg_args(PL_copy_term_ref(term0+2),types,argv,argc)
|
samer@0
|
615 && PL_call_predicate(NULL,PL_Q_NORMAL,call3,term0));
|
samer@0
|
616 }
|
samer@0
|
617
|
samer@0
|
618 static int prolog_handler_x(const char *path, const char *types, lo_arg **argv,
|
samer@0
|
619 int argc, lo_message msg, void *user_data)
|
samer@0
|
620 {
|
samer@0
|
621 term_t goal = PL_new_term_ref();
|
samer@0
|
622 term_t term0 = PL_new_term_refs(5);
|
samer@0
|
623 int rc=1;
|
samer@0
|
624
|
samer@0
|
625 lo_timetag ts = lo_message_get_timestamp(msg);
|
samer@0
|
626 lo_address sender = lo_message_get_source(msg);
|
samer@0
|
627 // printf("osc tt: %u s + %u micros\n",ts.sec,ts.frac);
|
samer@0
|
628
|
samer@0
|
629 PL_recorded((record_t)user_data,goal); // retrieve the goal term
|
samer@0
|
630 PL_put_term(term0,goal); // term_t goal encoded in user_data
|
samer@0
|
631 PL_put_atom_chars(term0+3,path);
|
samer@0
|
632
|
samer@0
|
633 if (ts.sec==0u) PL_put_atom(term0+2,osc_immed);
|
samer@0
|
634 else {
|
samer@0
|
635 rc=PL_unify_term( term0+2, PL_FUNCTOR, osc_ts_2,
|
samer@0
|
636 PL_INT64, (int64_t)ts.sec,
|
samer@0
|
637 PL_INT64, (int64_t)ts.frac);
|
samer@0
|
638 }
|
samer@0
|
639 // PL_put_float(term0+2, (double)(ts.sec-2208988800u) + ts.frac/4294967296.0);
|
samer@0
|
640
|
samer@0
|
641 return !( rc
|
samer@0
|
642 && unify_addr(term0+1,sender)
|
samer@0
|
643 && unify_msg_args(PL_copy_term_ref(term0+4),types,argv,argc)
|
samer@0
|
644 && PL_call_predicate(NULL,PL_Q_NORMAL,call5,term0));
|
samer@0
|
645 }
|
samer@0
|
646
|
samer@0
|
647 /*
|
samer@0
|
648 static int generic_handler(const char *path, const char *types, lo_arg **argv,
|
samer@0
|
649 int argc, lo_message msg, void *user_data)
|
samer@0
|
650 {
|
samer@0
|
651 int i;
|
samer@0
|
652
|
samer@0
|
653 printf("path: <%s>\n", path);
|
samer@0
|
654 for (i=0; i<argc; i++) {
|
samer@0
|
655 printf("arg %d '%c' ", i, types[i]);
|
samer@0
|
656 lo_arg_pp(types[i], argv[i]);
|
samer@0
|
657 printf("\n");
|
samer@0
|
658 }
|
samer@0
|
659 printf("\n");
|
samer@0
|
660 fflush(stdout);
|
samer@0
|
661 return 1;
|
samer@0
|
662 }
|
samer@0
|
663
|
samer@0
|
664 static int verbose_prolog_handler(const char *path, const char *types, lo_arg **argv,
|
samer@0
|
665 int argc, lo_message msg, void *user_data)
|
samer@0
|
666 {
|
samer@0
|
667 generic_handler(path,types,argv,argc,msg,user_data);
|
samer@0
|
668 prolog_handler(path,types,argv,argc,msg,user_data);
|
samer@0
|
669 return 1;
|
samer@0
|
670 }
|
samer@0
|
671 */
|
samer@0
|
672
|
samer@0
|
673 // run OSC server in this thread but with an extra message handler
|
samer@0
|
674 // to allow the /plosc/stop message to terminate the loop.
|
samer@0
|
675 static int run_stoppable_server(my_server_thread s, int timeout)
|
samer@0
|
676 {
|
samer@0
|
677 lo_server_add_method(s->s, "/plosc/stop", NULL, stop_handler, (void *)s);
|
samer@0
|
678 my_server_thread_run(s,timeout);
|
samer@0
|
679 lo_server_del_method(s->s,"/plosc/stop",NULL);
|
samer@0
|
680 return TRUE;
|
samer@0
|
681 }
|
samer@0
|
682
|
samer@0
|
683 foreign_t mk_server(term_t port, term_t server)
|
samer@0
|
684 {
|
samer@0
|
685 char *p;
|
samer@0
|
686
|
samer@0
|
687 if (PL_get_chars(port, &p, CVT_INTEGER)) {
|
samer@0
|
688 my_server_thread s = my_server_thread_new(p, server_error);
|
samer@0
|
689 if (s) return unify_server(server,s);
|
samer@0
|
690 else return FALSE;
|
samer@0
|
691 } else {
|
samer@0
|
692 return type_error(port,"integer");
|
samer@0
|
693 }
|
samer@0
|
694 }
|
samer@0
|
695
|
samer@0
|
696 foreign_t add_handler_x(term_t server, term_t msg, term_t types, term_t goal)
|
samer@0
|
697 {
|
samer@0
|
698 my_server_thread s;
|
samer@0
|
699 lo_method method;
|
samer@0
|
700 char *pattern, *typespec;
|
samer@0
|
701 char buffer[256]; // !! space for up to 255 arguments
|
samer@0
|
702 int rc;
|
samer@0
|
703
|
samer@0
|
704 rc = get_server(server,&s)
|
samer@0
|
705 && get_msg(msg,&pattern)
|
samer@0
|
706 && get_types(types,buffer,256,&typespec);
|
samer@0
|
707
|
samer@0
|
708 if (rc) {
|
samer@0
|
709 record_t goal_record=PL_record(goal);
|
samer@0
|
710 method = lo_server_add_method(s->s, pattern, typespec, prolog_handler_x, (void *)goal_record);
|
samer@0
|
711 }
|
samer@0
|
712 return rc;
|
samer@0
|
713 }
|
samer@0
|
714
|
samer@0
|
715 foreign_t add_handler(term_t server, term_t msg, term_t types, term_t goal)
|
samer@0
|
716 {
|
samer@0
|
717 my_server_thread s;
|
samer@0
|
718 lo_method method;
|
samer@0
|
719 char *pattern, *typespec;
|
samer@0
|
720 char buffer[256]; // !! space for up to 255 arguments
|
samer@0
|
721 int rc;
|
samer@0
|
722
|
samer@0
|
723 rc = get_server(server,&s)
|
samer@0
|
724 && get_msg(msg,&pattern)
|
samer@0
|
725 && get_types(types,buffer,256,&typespec);
|
samer@0
|
726
|
samer@0
|
727 if (rc) {
|
samer@0
|
728 record_t goal_record=PL_record(goal);
|
samer@0
|
729 method = lo_server_add_method(s->s, pattern, typespec, prolog_handler, (void *)goal_record);
|
samer@0
|
730 }
|
samer@0
|
731 return rc;
|
samer@0
|
732 }
|
samer@0
|
733
|
samer@0
|
734 foreign_t del_handler(term_t server, term_t msg, term_t types)
|
samer@0
|
735 {
|
samer@0
|
736 my_server_thread s;
|
samer@0
|
737 char *pattern, *typespec;
|
samer@0
|
738 char buffer[256]; // !! space for up to 255 arguments
|
samer@0
|
739 int rc;
|
samer@0
|
740
|
samer@0
|
741 rc = get_server(server,&s)
|
samer@0
|
742 && get_msg(msg,&pattern)
|
samer@0
|
743 && get_types(types,buffer,256,&typespec);
|
samer@0
|
744
|
samer@0
|
745 if (rc) lo_server_del_method(s->s,pattern,typespec);
|
samer@0
|
746 return rc;
|
samer@0
|
747 }
|
samer@0
|
748
|
samer@0
|
749 foreign_t start_server( term_t server)
|
samer@0
|
750 {
|
samer@0
|
751 my_server_thread s;
|
samer@0
|
752 return get_server(server,&s) && (my_server_thread_start(s)==0);
|
samer@0
|
753 }
|
samer@0
|
754
|
samer@0
|
755 foreign_t stop_server( term_t server)
|
samer@0
|
756 {
|
samer@0
|
757 my_server_thread s;
|
samer@0
|
758 return get_server(server,&s) && (my_server_thread_stop(s)==0);
|
samer@0
|
759 }
|
samer@0
|
760
|
samer@0
|
761 foreign_t run_server( term_t server)
|
samer@0
|
762 {
|
samer@0
|
763 my_server_thread s;
|
samer@0
|
764 printf("running OSC server synchronously...\n");
|
samer@0
|
765 return get_server(server,&s) && run_stoppable_server(s,10);
|
samer@0
|
766 }
|
samer@0
|
767
|
samer@0
|
768
|
samer@0
|
769 // -------------------------------------------------------------------------
|
samer@0
|
770 // my_server_thread implementation
|
samer@0
|
771
|
samer@0
|
772 my_server_thread my_server_thread_new(const char *port, lo_err_handler err_h)
|
samer@0
|
773 {
|
samer@0
|
774 my_server_thread st = malloc(sizeof(struct _my_server_thread));
|
samer@0
|
775 st->s = lo_server_new(port, err_h);
|
samer@0
|
776 st->active = 0;
|
samer@0
|
777 st->done = 0;
|
samer@0
|
778
|
samer@0
|
779 if (!st->s) {
|
samer@0
|
780 free(st);
|
samer@0
|
781 return NULL;
|
samer@0
|
782 }
|
samer@0
|
783 return st;
|
samer@0
|
784 }
|
samer@0
|
785
|
samer@0
|
786 void my_server_thread_free(my_server_thread st)
|
samer@0
|
787 {
|
samer@0
|
788 if (st) {
|
samer@0
|
789 if (st->active) {
|
samer@0
|
790 my_server_thread_stop(st);
|
samer@0
|
791 }
|
samer@0
|
792 lo_server_free(st->s);
|
samer@0
|
793 }
|
samer@0
|
794 free(st);
|
samer@0
|
795 }
|
samer@0
|
796
|
samer@0
|
797 int my_server_thread_stop(my_server_thread st)
|
samer@0
|
798 {
|
samer@0
|
799 int result;
|
samer@0
|
800
|
samer@0
|
801 if (st->active) {
|
samer@0
|
802 st->active = 0; // Signal thread to stop
|
samer@0
|
803
|
samer@0
|
804 result = pthread_join( st->thread, NULL );
|
samer@0
|
805 if (result) {
|
samer@0
|
806 fprintf(stderr,"Failed to stop thread: pthread_join(), %s",strerror(result));
|
samer@0
|
807 return -result;
|
samer@0
|
808 }
|
samer@0
|
809 }
|
samer@0
|
810
|
samer@0
|
811 return 0;
|
samer@0
|
812 }
|
samer@0
|
813
|
samer@0
|
814
|
samer@0
|
815 int my_server_thread_start(my_server_thread st)
|
samer@0
|
816 {
|
samer@0
|
817 int result;
|
samer@0
|
818
|
samer@0
|
819 if (!st->active) {
|
samer@0
|
820 st->active = 1;
|
samer@0
|
821 st->done = 0;
|
samer@0
|
822
|
samer@0
|
823 // Create the server thread
|
samer@0
|
824 result = pthread_create(&(st->thread), NULL, (void *)&prolog_thread_func, st);
|
samer@0
|
825 if (result) {
|
samer@0
|
826 fprintf(stderr, "Failed to create thread: pthread_create(), %s",
|
samer@0
|
827 strerror(result));
|
samer@0
|
828 return -result;
|
samer@0
|
829 }
|
samer@0
|
830
|
samer@0
|
831 }
|
samer@0
|
832 return 0;
|
samer@0
|
833 }
|
samer@0
|
834
|
samer@0
|
835 int my_server_thread_run(my_server_thread st, int timeout)
|
samer@0
|
836 {
|
samer@0
|
837 st->active = 1;
|
samer@0
|
838 st->done = 0;
|
samer@0
|
839 while (st->active) {
|
samer@0
|
840 lo_server_recv_noblock(st->s, timeout);
|
samer@0
|
841 }
|
samer@0
|
842 st->done = 1;
|
samer@0
|
843 return 0;
|
samer@0
|
844 }
|
samer@0
|
845
|
samer@0
|
846 // code for the asynchronous server loop
|
samer@0
|
847 // we must create and attach a new Prolog engine to enable
|
samer@0
|
848 // calls to Prolog from this thread.
|
samer@0
|
849 static void prolog_thread_func(void *data)
|
samer@0
|
850 {
|
samer@0
|
851 my_server_thread st = (my_server_thread)data;
|
samer@0
|
852
|
samer@0
|
853 printf("OSC server started.\n");
|
samer@0
|
854 PL_thread_attach_engine(NULL);
|
samer@0
|
855 my_server_thread_run(st,50);
|
samer@0
|
856 PL_thread_destroy_engine();
|
samer@0
|
857 printf("OSC server stopped.\n");
|
samer@0
|
858 pthread_exit(NULL);
|
samer@0
|
859 }
|
samer@0
|
860
|