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