Fix getPostFile() upload function, return bare JSON in list
[src/app-framework-binder.git] / plugins / media / media-rygel.c
1 /*
2  * Copyright (C) 2016 "IoT.bzh"
3  * Author "Manuel Bachmann"
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18
19 #include "media-api.h"
20
21 /* -------------- MEDIA RYGEL IMPLEMENTATION ---------------- */
22
23 /* --- PUBLIC FUNCTIONS --- */
24
25 PUBLIC unsigned char _rygel_init (mediaCtxHandleT *ctx) {
26
27     GMainContext *loop;
28     GUPnPContext *context;
29     GUPnPControlPoint *control_point;
30     gint handler_cb;
31     struct timeval tv_start, tv_now;
32
33     context = gupnp_context_new (NULL, NULL, 0, NULL);
34
35     control_point = gupnp_control_point_new (context, URN_MEDIA_SERVER);
36
37     handler_cb = g_signal_connect (control_point, "device-proxy-available",
38                                    G_CALLBACK (_rygel_device_cb), ctx);
39
40     /* start searching for servers */
41     gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
42
43     loop = g_main_context_default ();
44
45     /* 5 seconds should be sufficient to find Rygel */
46     gettimeofday (&tv_start, NULL);
47     gettimeofday (&tv_now, NULL);
48     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
49
50         g_main_context_iteration (loop, FALSE);
51
52         if (ctx->media_server)
53             break;
54         gettimeofday (&tv_now, NULL);
55     }
56     /* fail if we found no server */
57     if (!ctx->media_server)
58       return 0;
59
60     /* we have found the server ; stop looking for it... */
61     g_signal_handler_disconnect (control_point, handler_cb);
62
63     dev_ctx[client_count]->loop = loop;
64     dev_ctx[client_count]->context = context;
65     dev_ctx[client_count]->av_transport = NULL;
66     dev_ctx[client_count]->state = STOP;
67     dev_ctx[client_count]->target_state = STOP;
68     dev_ctx[client_count]->action_args = NULL;
69     dev_ctx[client_count]->transfer_started = 0;
70
71     client_count++;
72
73     return 1;
74 }
75
76 PUBLIC void _rygel_free (mediaCtxHandleT *ctx) {
77
78     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
79
80     client_count--;
81
82     g_main_context_unref (dev_ctx_c->loop);
83     dev_ctx_c->loop = NULL;
84     dev_ctx_c->context = NULL;
85     dev_ctx_c->device_info = NULL;
86     dev_ctx_c->av_transport = NULL;
87     dev_ctx_c->content_dir = NULL;
88     dev_ctx_c->content_res = NULL;
89 }
90
91 PUBLIC json_object* _rygel_list (mediaCtxHandleT *ctx) {
92
93     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
94     json_object *json_o, *json_a;
95     char *raw, *start, *end, *id, *title;
96     int length, i = 0;
97
98     if (!dev_ctx_c)
99       return NULL;
100
101     raw = _rygel_list_raw (dev_ctx_c, NULL);
102     if (!raw)
103       return NULL;
104
105     start = strstr (raw, "<dc:title>");
106     if (!start)
107       return NULL;
108
109     json_o = json_object_new_object ();
110     json_a = json_object_new_array ();
111     while (start) {
112         json_object *json_i, *json_id, *json_title;
113
114         start = strstr (start, "<dc:title>");
115         if (!start) break;
116         end = strstr (start, "</dc:title>");
117         start += 10;
118         length = end - start;
119
120         asprintf (&id, "%02d", i);
121
122         title = (char*) malloc (length+1);
123         strncpy (title, start, length);
124         title[length] = '\0';
125
126         json_i = json_object_new_object ();
127         json_id = json_object_new_string (id);
128         json_title = json_object_new_string (title);
129         json_object_object_add (json_i, "id", json_id);
130         json_object_object_add (json_i, "title", json_title);
131         json_object_array_add (json_a, json_i);
132
133         free (id); free (title); 
134         i++;
135     }
136
137     json_object_object_add (json_o, "list", json_a);
138
139     return json_o;
140 }
141
142 PUBLIC unsigned char _rygel_select (mediaCtxHandleT *ctx, unsigned int index) {
143
144     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
145     unsigned int count;
146
147     if (!dev_ctx_c)
148       return 0;
149
150     if (!_rygel_list_raw (dev_ctx_c, &count) ||
151         index >= count)
152       return 0;
153
154     if (ctx->index != index)
155       dev_ctx_c->state = STOP;
156
157     return 1;
158 }
159
160 PUBLIC unsigned char _rygel_upload (mediaCtxHandleT *ctx, char *path) {
161
162     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
163     char *raw, *upload_id;
164
165     if (!dev_ctx_c)
166       return 0;
167
168     raw = _rygel_list_raw (dev_ctx_c, NULL);
169     if (!raw)
170       return 0;
171
172     /* for now, we always use the same upload container id */
173     upload_id = _rygel_find_upload_id (dev_ctx_c, raw);
174
175     return _rygel_start_uploading (dev_ctx_c, path, upload_id);
176 }
177
178 PUBLIC unsigned char _rygel_do (mediaCtxHandleT *ctx, State state, char *args) {
179
180     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
181     unsigned int index = ctx->index;
182     unsigned int count;
183     char *raw, *id, *metadata, *uri;
184
185     if (!dev_ctx_c || dev_ctx_c->state == state)
186         return 0;
187
188     raw = _rygel_list_raw (dev_ctx_c, &count);
189     if (!raw || index >= count)
190       return 0;
191
192           id = _rygel_find_id_for_index (dev_ctx_c, raw, index);
193     metadata = _rygel_find_metadata_for_id (dev_ctx_c, id);
194          uri = _rygel_find_uri_for_metadata (dev_ctx_c, metadata);
195
196     return _rygel_start_doing (dev_ctx_c, uri, metadata, state, args);
197 }
198
199 /* --- LOCAL HELPER FUNCTIONS --- */
200
201 STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
202
203     GUPnPServiceProxy *content_dir_proxy;
204     struct timeval tv_start, tv_now;
205
206     dev_ctx_c->content_res = NULL;
207     dev_ctx_c->content_num = 0;
208     content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
209
210     gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
211                                       "ObjectID", G_TYPE_STRING, "Filesystem",
212                                       "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
213                                       "Filter", G_TYPE_STRING, "@childCount",
214                                       "StartingIndex", G_TYPE_UINT, 0,
215                                       "RequestedCount", G_TYPE_UINT, 64,
216                                       "SortCriteria", G_TYPE_STRING, "",
217                                        NULL);
218
219     gettimeofday (&tv_start, NULL);
220     gettimeofday (&tv_now, NULL);
221     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
222
223         g_main_context_iteration (dev_ctx_c->loop, FALSE);
224
225         if (dev_ctx_c->content_res)
226             break;
227         gettimeofday (&tv_now, NULL);
228     }
229
230     if (count) *count = dev_ctx_c->content_num;
231     return dev_ctx_c->content_res;
232 }
233
234 STATIC char* _rygel_find_upload_id (dev_ctx_T* dev_ctx_c, char *raw) {
235
236     char *found;
237     char id[33];
238
239     found = strstr (raw, "parentID=\"");
240     found += 10;
241
242     /* IDs are 32-bit strings */
243     strncpy (id, found, 32);
244     id[32] = '\0';
245
246     return strdup (id);
247 }
248
249 STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
250
251     char *found = raw;
252     char id[33];
253     int i;
254
255     for (i = 0; i <= index; i++) {
256         found = strstr (found, "item id=");
257         found += 9;
258
259         if (i == index) {
260             /* IDs are 32-bit strings */
261             strncpy (id, found, 32);
262             id[32] = '\0';
263         }
264     }
265
266     return strdup (id);
267 }
268
269 STATIC char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
270
271     GUPnPServiceProxy *content_dir_proxy;
272     struct timeval tv_start, tv_now;
273
274     dev_ctx_c->content_res = NULL;
275
276     content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
277
278     gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_metadata_cb, dev_ctx_c,
279                                       "ObjectID", G_TYPE_STRING, id,
280                                       "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
281                                       "Filter", G_TYPE_STRING, "*",
282                                       "StartingIndex", G_TYPE_UINT, 0,
283                                       "RequestedCount", G_TYPE_UINT, 0,
284                                       "SortCriteria", G_TYPE_STRING, "",
285                                        NULL);
286
287     gettimeofday (&tv_start, NULL);
288     gettimeofday (&tv_now, NULL);
289     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
290
291         g_main_context_iteration (dev_ctx_c->loop, FALSE);
292
293         if (dev_ctx_c->content_res)
294             break;
295         gettimeofday (&tv_now, NULL);
296     }
297
298     return dev_ctx_c->content_res;
299 }
300
301 STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
302
303     char *start, *end, *uri = NULL;
304     int length;
305
306     /* position ourselves after the first "<res " tag */
307     start = strstr (metadata, "<res ");
308
309     while (start) {
310         start = strstr (start, "http://");
311         if (!start) break;
312         end = strstr (start, "</res>");
313         length = end - start;
314
315
316         uri = (char *)malloc (length + 1);
317         strncpy (uri, start, length);
318         uri[length] = '\0';
319         /* if the URI contains "primary_http", it is the main one ; stop here...*/
320         if (strstr (uri, "primary_http"))
321           break;
322
323         free (uri); start = end;
324     }
325
326     return uri;
327 }
328
329 STATIC char * _rygel_time_for_string (char *string) {
330
331     int total_seconds;
332     unsigned int hours, minutes, seconds;
333     char *time;
334
335     total_seconds = atoi (string);
336     hours = total_seconds / 3600;
337     minutes = (total_seconds / 60) - (hours * 60);
338     seconds = total_seconds - (hours * 3600) - (minutes * 60);
339
340     asprintf (&time, "%u:%02u:%02u", hours, minutes, seconds);
341
342     return time;
343 }
344
345 STATIC unsigned char _rygel_start_uploading (dev_ctx_T* dev_ctx_c, char *path, char *upload_id) {
346
347     GUPnPServiceProxy *content_dir_proxy;
348     GUPnPDIDLLiteWriter *didl_writer;
349     GUPnPDIDLLiteObject *didl_object;
350     char *didl, *content_type, *mime_type, *upnp_class;
351     struct timeval tv_start, tv_now;
352
353     didl_writer = gupnp_didl_lite_writer_new (NULL);
354     didl_object = GUPNP_DIDL_LITE_OBJECT (gupnp_didl_lite_writer_add_item (didl_writer));
355
356     /* create the metadata for the file */
357     gupnp_didl_lite_object_set_parent_id (didl_object, upload_id);
358     gupnp_didl_lite_object_set_id (didl_object, "");
359     gupnp_didl_lite_object_set_restricted (didl_object, FALSE);
360     gupnp_didl_lite_object_set_title (didl_object, g_path_get_basename (path));
361     /* deduce the UPnP class from the MIME type ("audio/ogg" e.g.) */
362     content_type = g_content_type_guess (path, NULL, 0, NULL);
363     mime_type = g_content_type_get_mime_type (content_type);
364     if (strstr (mime_type, "audio/"))
365       upnp_class = strdup ("object.item.audioItem.musicTrack");
366     else if (strstr (mime_type, "video/"))
367       upnp_class = strdup ("object.item.videoItem");
368     else if (strstr (mime_type, "image/"))
369       upnp_class = strdup ("object.item.imageItem");
370     else
371       upnp_class = strdup ("object.item");
372     gupnp_didl_lite_object_set_upnp_class (didl_object, upnp_class);
373     didl = gupnp_didl_lite_writer_get_string (didl_writer);
374
375     dev_ctx_c->transfer_path = path;
376     dev_ctx_c->transfer_started = 0;
377     content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
378
379     gupnp_service_proxy_begin_action (content_dir_proxy, "CreateObject", _rygel_upload_cb, dev_ctx_c,
380                                       "ContainerID", G_TYPE_STRING, upload_id,
381                                       "Elements", G_TYPE_STRING, didl,
382                                        NULL);
383
384     gettimeofday (&tv_start, NULL);
385     gettimeofday (&tv_now, NULL);
386     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
387
388       g_main_context_iteration (dev_ctx_c->loop, FALSE);
389
390       if (dev_ctx_c->transfer_started)
391         break;
392       gettimeofday (&tv_now, NULL);
393     }
394     if (!dev_ctx_c->transfer_started)
395       return 0;
396
397     return 1;
398 }
399
400 STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state, char *args) {
401
402     GUPnPServiceProxy *av_transport_proxy;
403     struct timeval tv_start, tv_now;
404
405     if (!dev_ctx_c->av_transport) {
406       if (!_rygel_find_av_transport (dev_ctx_c))
407          return 0;
408     }
409     dev_ctx_c->target_state = state;
410     dev_ctx_c->action_args = args;
411     av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
412
413     gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c,
414                                       "InstanceID", G_TYPE_UINT, 0,
415                                       "CurrentURI", G_TYPE_STRING, uri,
416                                       "CurrentURIMetaData", G_TYPE_STRING, metadata,
417                                        NULL);
418
419     gettimeofday (&tv_start, NULL);
420     gettimeofday (&tv_now, NULL);
421     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
422
423       g_main_context_iteration (dev_ctx_c->loop, FALSE);
424
425       if (dev_ctx_c->state == state)
426         break;
427       gettimeofday (&tv_now, NULL);
428     }
429     if (dev_ctx_c->state != state)
430       return 0;
431
432     return 1;
433 }
434
435 STATIC unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
436
437     GUPnPControlPoint *control_point;
438     gint handler_cb;
439     struct timeval tv_start, tv_now;
440
441     control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
442
443     handler_cb = g_signal_connect (control_point, "device-proxy-available",
444                                    G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
445
446     gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
447
448     gettimeofday (&tv_start, NULL);
449     gettimeofday (&tv_now, NULL);
450     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
451
452         g_main_context_iteration (dev_ctx_c->loop, FALSE);
453
454         if (dev_ctx_c->av_transport)
455             break;
456         gettimeofday (&tv_now, NULL);
457     }
458     g_signal_handler_disconnect (control_point, handler_cb);
459
460     if (!dev_ctx_c->av_transport)
461       return 0;
462
463     return 1;
464 }
465
466
467  /* ---- LOCAL CALLBACK FUNCTIONS ---- */
468
469 STATIC void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
470                               gpointer data) {
471
472     mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
473     GUPnPDeviceInfo *device_info;
474     GUPnPServiceInfo *content_dir;
475     const char *device_name;
476
477     device_info = GUPNP_DEVICE_INFO (proxy);
478     device_name = gupnp_device_info_get_model_name (device_info);
479     content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR);
480
481     if (strcmp (device_name, "Rygel") != 0)
482         return;
483     if (!content_dir)
484         return;
485
486     /* allocate the global array if it has not been not done */
487     if (!dev_ctx)
488         dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
489     else
490         dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
491
492     /* create an element for the client in the global array */
493     dev_ctx[client_count] = (dev_ctx_T*) malloc (sizeof(dev_ctx_T));
494     dev_ctx[client_count]->device_info = device_info;
495     dev_ctx[client_count]->content_dir = content_dir;
496
497     /* make the client context aware of it */
498     ctx->media_server = (void*)dev_ctx[client_count];
499 }
500
501 STATIC void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
502                                     gpointer data) {
503
504     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
505     GUPnPDeviceInfo *device_info;
506     GUPnPServiceInfo *av_transport;
507
508     device_info = GUPNP_DEVICE_INFO (proxy);
509     av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
510
511     dev_ctx_c->av_transport = av_transport;
512 }
513
514 STATIC void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
515                                gpointer data) {
516
517     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
518     GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
519     GError *error;
520     char *result;
521     guint32 number_returned;
522     guint32 total_matches;
523     char *found;
524     char subid[33];
525
526     gupnp_service_proxy_end_action (content_dir, action, &error,
527                                     "Result", G_TYPE_STRING, &result,
528                                     "NumberReturned", G_TYPE_UINT, &number_returned,
529                                     "TotalMatches", G_TYPE_UINT, &total_matches,
530                                      NULL);
531
532     if (number_returned == 0)
533         return;
534
535     if (number_returned == 1) {
536         found = strstr (result, "id=\"");       
537         found += 4;
538         strncpy (subid, found, 32); subid[32] = '\0';
539
540         gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
541                                           "ObjectID", G_TYPE_STRING, subid,
542                                           "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
543                                           "Filter", G_TYPE_STRING, "@childCount",
544                                           "StartingIndex", G_TYPE_UINT, 0,
545                                           "RequestedCount", G_TYPE_UINT, 64,
546                                           "SortCriteria", G_TYPE_STRING, "",
547                                            NULL);
548         return;
549     }
550
551     if (number_returned > 1) {
552         dev_ctx_c->content_res = result;
553         dev_ctx_c->content_num = number_returned;
554     }
555 }
556
557 STATIC void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
558                                 gpointer data) {
559
560     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
561     GError *error;
562     char *result;
563
564     gupnp_service_proxy_end_action (content_dir, action, &error,
565                                     "Result", G_TYPE_STRING, &result,
566                                      NULL);
567
568     dev_ctx_c->content_res = result;
569 }
570
571 STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
572                               gpointer data)
573 {
574
575     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
576     GUPnPServiceProxy *av_transport_proxy;
577     GError *error;
578     char *time;
579     struct timeval tv_start, tv_now;
580
581     av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
582
583     gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
584
585     switch (dev_ctx_c->target_state) {
586         case PLAY:
587           gupnp_service_proxy_begin_action (av_transport_proxy, "Play", _rygel_do_cb, dev_ctx_c,
588                                            "InstanceID", G_TYPE_UINT, 0,
589                                            "Speed", G_TYPE_STRING, "1",
590                                             NULL);
591           break;
592        case PAUSE:
593           gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
594                                            "InstanceID", G_TYPE_UINT, 0,
595                                             NULL);
596           break;
597        case STOP:
598           gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
599                                            "InstanceID", G_TYPE_UINT, 0,
600                                             NULL);
601           break;
602        case SEEK:
603           time = _rygel_time_for_string (dev_ctx_c->action_args);
604           gupnp_service_proxy_begin_action (av_transport_proxy, "Seek", _rygel_do_cb, dev_ctx_c,
605                                            "InstanceID", G_TYPE_UINT, 0,
606                                            "Unit", G_TYPE_STRING, "ABS_TIME",
607                                            "Target", G_TYPE_STRING, time,
608                                             NULL);
609        default:
610          break;
611     }
612
613     gettimeofday (&tv_start, NULL);
614     gettimeofday (&tv_now, NULL);
615     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
616
617         g_main_context_iteration (dev_ctx_c->loop, FALSE);
618
619         if (dev_ctx_c->state == dev_ctx_c->target_state)
620             break;
621         gettimeofday (&tv_now, NULL);
622     }
623 }
624
625 STATIC void _rygel_upload_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
626                               gpointer data)
627 {
628     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
629     GUPnPServiceProxy *content_dir_proxy;
630     GError *error;
631     char *result, *start, *end, *dst_uri, *src_uri;
632     int length;
633     struct timeval tv_start, tv_now;
634
635     content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
636
637     if (!gupnp_service_proxy_end_action (content_dir, action, &error,
638                                          "Result", G_TYPE_STRING, &result,
639                                           NULL))
640       return;
641
642     start = strstr (result, "<res importUri=\"");
643     if (!start)
644       return;
645
646     start += 16;
647     end = strstr (start, "\"");
648     length = end - start;
649
650     dst_uri = (char*) malloc(length+1);
651     strncpy (dst_uri, start, length);
652     dst_uri[length] = '\0';
653
654     asprintf (&src_uri, "http://%s:%u%s", gupnp_context_get_host_ip (dev_ctx_c->context),
655                                           gupnp_context_get_port (dev_ctx_c->context),
656                                           dev_ctx_c->transfer_path);
657
658     /* host the file */
659     gupnp_context_host_path (dev_ctx_c->context, dev_ctx_c->transfer_path,
660                                                  dev_ctx_c->transfer_path);
661
662     gupnp_service_proxy_begin_action (content_dir_proxy, "ImportResource", _rygel_transfer_cb, dev_ctx_c,
663                                       "SourceURI", G_TYPE_STRING, src_uri,
664                                       "DestinationURI", G_TYPE_STRING, dst_uri,
665                                        NULL);
666
667     gettimeofday (&tv_start, NULL);
668     gettimeofday (&tv_now, NULL);
669     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
670
671         g_main_context_iteration (dev_ctx_c->loop, FALSE);
672
673         if (dev_ctx_c->transfer_started)
674             break;
675         gettimeofday (&tv_now, NULL);
676     }
677 }
678
679 STATIC void _rygel_transfer_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
680                                 gpointer data)
681 {
682     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
683     GError *error;
684     guint transfer_id;
685
686     if (!gupnp_service_proxy_end_action (content_dir, action, &error,
687                                          "TransferID", G_TYPE_UINT, &transfer_id,
688                                           NULL))
689       return;
690
691     dev_ctx_c->transfer_started = 1;
692 }
693
694 STATIC void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
695                           gpointer data)
696 {
697     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
698     GError *error;
699
700     if (!gupnp_service_proxy_end_action (av_transport, action, &error,
701                                          NULL))
702       return;
703
704     dev_ctx_c->state = dev_ctx_c->target_state;
705 }