Fix Media Plugin refresh, add seek API
[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 char* _rygel_list (mediaCtxHandleT *ctx) {
92
93     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
94     char *raw, *start, *end, *title, *result = NULL;
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     result = strdup("");
109     while (start) {
110         start = strstr (start, "<dc:title>");
111         if (!start) break;
112         end = strstr (start, "</dc:title>");
113         start += 10;
114         length = end - start;
115
116         title = (char*) malloc (length+1);
117         strncpy (title, start, length);
118         title[length] = '\0';
119
120         asprintf (&result, "%s%02d:%s::", result, i, title);
121
122         free (title); i++;
123     }
124
125     return result;
126 }
127
128 PUBLIC unsigned char _rygel_select (mediaCtxHandleT *ctx, unsigned int index) {
129
130     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
131     unsigned int count;
132
133     if (!dev_ctx_c)
134       return 0;
135
136     if (!_rygel_list_raw (dev_ctx_c, &count) ||
137         index >= count)
138       return 0;
139
140     if (ctx->index != index)
141       dev_ctx_c->state = STOP;
142
143     return 1;
144 }
145
146 PUBLIC unsigned char _rygel_upload (mediaCtxHandleT *ctx, char *path) {
147
148     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
149     char *raw, *upload_id;
150
151     if (!dev_ctx_c)
152       return 0;
153
154     raw = _rygel_list_raw (dev_ctx_c, NULL);
155     if (!raw)
156       return 0;
157
158     /* for now, we always use the same upload container id */
159     upload_id = _rygel_find_upload_id (dev_ctx_c, raw);
160
161     return _rygel_start_uploading (dev_ctx_c, path, upload_id);
162 }
163
164 PUBLIC unsigned char _rygel_do (mediaCtxHandleT *ctx, State state, char *args) {
165
166     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server;
167     unsigned int index = ctx->index;
168     unsigned int count;
169     char *raw, *id, *metadata, *uri;
170
171     if (!dev_ctx_c || dev_ctx_c->state == state)
172         return 0;
173
174     raw = _rygel_list_raw (dev_ctx_c, &count);
175     if (!raw || index >= count)
176       return 0;
177
178           id = _rygel_find_id_for_index (dev_ctx_c, raw, index);
179     metadata = _rygel_find_metadata_for_id (dev_ctx_c, id);
180          uri = _rygel_find_uri_for_metadata (dev_ctx_c, metadata);
181
182     return _rygel_start_doing (dev_ctx_c, uri, metadata, state, args);
183 }
184
185 /* --- LOCAL HELPER FUNCTIONS --- */
186
187 STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) {
188
189     GUPnPServiceProxy *content_dir_proxy;
190     struct timeval tv_start, tv_now;
191
192     dev_ctx_c->content_res = NULL;
193     dev_ctx_c->content_num = 0;
194     content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
195
196     gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
197                                       "ObjectID", G_TYPE_STRING, "Filesystem",
198                                       "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
199                                       "Filter", G_TYPE_STRING, "@childCount",
200                                       "StartingIndex", G_TYPE_UINT, 0,
201                                       "RequestedCount", G_TYPE_UINT, 64,
202                                       "SortCriteria", G_TYPE_STRING, "",
203                                        NULL);
204
205     gettimeofday (&tv_start, NULL);
206     gettimeofday (&tv_now, NULL);
207     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
208
209         g_main_context_iteration (dev_ctx_c->loop, FALSE);
210
211         if (dev_ctx_c->content_res)
212             break;
213         gettimeofday (&tv_now, NULL);
214     }
215
216     if (count) *count = dev_ctx_c->content_num;
217     return dev_ctx_c->content_res;
218 }
219
220 STATIC char* _rygel_find_upload_id (dev_ctx_T* dev_ctx_c, char *raw) {
221
222     char *found;
223     char id[33];
224
225     found = strstr (raw, "parentID=\"");
226     found += 10;
227
228     /* IDs are 32-bit strings */
229     strncpy (id, found, 32);
230     id[32] = '\0';
231
232     return strdup (id);
233 }
234
235 STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) {
236
237     char *found = raw;
238     char id[33];
239     int i;
240
241     for (i = 0; i <= index; i++) {
242         found = strstr (found, "item id=");
243         found += 9;
244
245         if (i == index) {
246             /* IDs are 32-bit strings */
247             strncpy (id, found, 32);
248             id[32] = '\0';
249         }
250     }
251
252     return strdup (id);
253 }
254
255 STATIC char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) {
256
257     GUPnPServiceProxy *content_dir_proxy;
258     struct timeval tv_start, tv_now;
259
260     dev_ctx_c->content_res = NULL;
261
262     content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
263
264     gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_metadata_cb, dev_ctx_c,
265                                       "ObjectID", G_TYPE_STRING, id,
266                                       "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
267                                       "Filter", G_TYPE_STRING, "*",
268                                       "StartingIndex", G_TYPE_UINT, 0,
269                                       "RequestedCount", G_TYPE_UINT, 0,
270                                       "SortCriteria", G_TYPE_STRING, "",
271                                        NULL);
272
273     gettimeofday (&tv_start, NULL);
274     gettimeofday (&tv_now, NULL);
275     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
276
277         g_main_context_iteration (dev_ctx_c->loop, FALSE);
278
279         if (dev_ctx_c->content_res)
280             break;
281         gettimeofday (&tv_now, NULL);
282     }
283
284     return dev_ctx_c->content_res;
285 }
286
287 STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) {
288
289     char *start, *end, *uri = NULL;
290     int length;
291
292     /* position ourselves after the first "<res " tag */
293     start = strstr (metadata, "<res ");
294
295     while (start) {
296         start = strstr (start, "http://");
297         if (!start) break;
298         end = strstr (start, "</res>");
299         length = end - start;
300
301
302         uri = (char *)malloc (length + 1);
303         strncpy (uri, start, length);
304         uri[length] = '\0';
305         /* if the URI contains "primary_http", it is the main one ; stop here...*/
306         if (strstr (uri, "primary_http"))
307           break;
308
309         free (uri); start = end;
310     }
311
312     return uri;
313 }
314
315 STATIC char * _rygel_time_for_string (char *string) {
316
317     int total_seconds;
318     unsigned int hours, minutes, seconds;
319     char *time;
320
321     total_seconds = atoi (string);
322     hours = total_seconds / 3600;
323     minutes = (total_seconds / 60) - (hours * 60);
324     seconds = total_seconds - (hours * 3600) - (minutes * 60);
325
326     asprintf (&time, "%u:%02u:%02u", hours, minutes, seconds);
327
328     return time;
329 }
330
331 STATIC unsigned char _rygel_start_uploading (dev_ctx_T* dev_ctx_c, char *path, char *upload_id) {
332
333     GUPnPServiceProxy *content_dir_proxy;
334     GUPnPDIDLLiteWriter *didl_writer;
335     GUPnPDIDLLiteObject *didl_object;
336     char *didl, *content_type, *mime_type, *upnp_class;
337     struct timeval tv_start, tv_now;
338
339     didl_writer = gupnp_didl_lite_writer_new (NULL);
340     didl_object = GUPNP_DIDL_LITE_OBJECT (gupnp_didl_lite_writer_add_item (didl_writer));
341
342     /* create the metadata for the file */
343     gupnp_didl_lite_object_set_parent_id (didl_object, upload_id);
344     gupnp_didl_lite_object_set_id (didl_object, "");
345     gupnp_didl_lite_object_set_restricted (didl_object, FALSE);
346     gupnp_didl_lite_object_set_title (didl_object, g_path_get_basename (path));
347     /* deduce the UPnP class from the MIME type ("audio/ogg" e.g.) */
348     content_type = g_content_type_guess (path, NULL, 0, NULL);
349     mime_type = g_content_type_get_mime_type (content_type);
350     if (strstr (mime_type, "audio/"))
351       upnp_class = strdup ("object.item.audioItem.musicTrack");
352     else if (strstr (mime_type, "video/"))
353       upnp_class = strdup ("object.item.videoItem");
354     else if (strstr (mime_type, "image/"))
355       upnp_class = strdup ("object.item.imageItem");
356     else
357       upnp_class = strdup ("object.item");
358     gupnp_didl_lite_object_set_upnp_class (didl_object, upnp_class);
359     didl = gupnp_didl_lite_writer_get_string (didl_writer);
360
361     dev_ctx_c->transfer_path = path;
362     dev_ctx_c->transfer_started = 0;
363     content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir);
364
365     gupnp_service_proxy_begin_action (content_dir_proxy, "CreateObject", _rygel_upload_cb, dev_ctx_c,
366                                       "ContainerID", G_TYPE_STRING, upload_id,
367                                       "Elements", G_TYPE_STRING, didl,
368                                        NULL);
369
370     gettimeofday (&tv_start, NULL);
371     gettimeofday (&tv_now, NULL);
372     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
373
374       g_main_context_iteration (dev_ctx_c->loop, FALSE);
375
376       if (dev_ctx_c->transfer_started)
377         break;
378       gettimeofday (&tv_now, NULL);
379     }
380     if (!dev_ctx_c->transfer_started)
381       return 0;
382
383     return 1;
384 }
385
386 STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state, char *args) {
387
388     GUPnPServiceProxy *av_transport_proxy;
389     struct timeval tv_start, tv_now;
390
391     if (!dev_ctx_c->av_transport) {
392       if (!_rygel_find_av_transport (dev_ctx_c))
393          return 0;
394     }
395     dev_ctx_c->target_state = state;
396     dev_ctx_c->action_args = args;
397     av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport);
398
399     gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c,
400                                       "InstanceID", G_TYPE_UINT, 0,
401                                       "CurrentURI", G_TYPE_STRING, uri,
402                                       "CurrentURIMetaData", G_TYPE_STRING, metadata,
403                                        NULL);
404
405     gettimeofday (&tv_start, NULL);
406     gettimeofday (&tv_now, NULL);
407     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
408
409       g_main_context_iteration (dev_ctx_c->loop, FALSE);
410
411       if (dev_ctx_c->state == state)
412         break;
413       gettimeofday (&tv_now, NULL);
414     }
415     if (dev_ctx_c->state != state)
416       return 0;
417
418     return 1;
419 }
420
421 STATIC unsigned char _rygel_find_av_transport (dev_ctx_T* dev_ctx_c) {
422
423     GUPnPControlPoint *control_point;
424     gint handler_cb;
425     struct timeval tv_start, tv_now;
426
427     control_point = gupnp_control_point_new (dev_ctx_c->context, URN_MEDIA_RENDERER);
428
429     handler_cb = g_signal_connect (control_point, "device-proxy-available",
430                                    G_CALLBACK (_rygel_av_transport_cb), dev_ctx_c);
431
432     gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (control_point), TRUE);
433
434     gettimeofday (&tv_start, NULL);
435     gettimeofday (&tv_now, NULL);
436     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
437
438         g_main_context_iteration (dev_ctx_c->loop, FALSE);
439
440         if (dev_ctx_c->av_transport)
441             break;
442         gettimeofday (&tv_now, NULL);
443     }
444     g_signal_handler_disconnect (control_point, handler_cb);
445
446     if (!dev_ctx_c->av_transport)
447       return 0;
448
449     return 1;
450 }
451
452
453  /* ---- LOCAL CALLBACK FUNCTIONS ---- */
454
455 STATIC void _rygel_device_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
456                               gpointer data) {
457
458     mediaCtxHandleT *ctx = (mediaCtxHandleT*)data;
459     GUPnPDeviceInfo *device_info;
460     GUPnPServiceInfo *content_dir;
461     const char *device_name;
462
463     device_info = GUPNP_DEVICE_INFO (proxy);
464     device_name = gupnp_device_info_get_model_name (device_info);
465     content_dir = gupnp_device_info_get_service (device_info, URN_CONTENT_DIR);
466
467     if (strcmp (device_name, "Rygel") != 0)
468         return;
469     if (!content_dir)
470         return;
471
472     /* allocate the global array if it has not been not done */
473     if (!dev_ctx)
474         dev_ctx = (dev_ctx_T**) malloc (sizeof(dev_ctx_T));
475     else
476         dev_ctx = (dev_ctx_T**) realloc (dev_ctx, (client_count+1)*sizeof(dev_ctx_T));
477
478     /* create an element for the client in the global array */
479     dev_ctx[client_count] = (dev_ctx_T*) malloc (sizeof(dev_ctx_T));
480     dev_ctx[client_count]->device_info = device_info;
481     dev_ctx[client_count]->content_dir = content_dir;
482
483     /* make the client context aware of it */
484     ctx->media_server = (void*)dev_ctx[client_count];
485 }
486
487 STATIC void _rygel_av_transport_cb (GUPnPControlPoint *point, GUPnPDeviceProxy *proxy,
488                                     gpointer data) {
489
490     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
491     GUPnPDeviceInfo *device_info;
492     GUPnPServiceInfo *av_transport;
493
494     device_info = GUPNP_DEVICE_INFO (proxy);
495     av_transport = gupnp_device_info_get_service (device_info, URN_AV_TRANSPORT);
496
497     dev_ctx_c->av_transport = av_transport;
498 }
499
500 STATIC void _rygel_content_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
501                                gpointer data) {
502
503     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
504     GUPnPServiceProxy *content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
505     GError *error;
506     char *result;
507     guint32 number_returned;
508     guint32 total_matches;
509     char *found;
510     char subid[33];
511
512     gupnp_service_proxy_end_action (content_dir, action, &error,
513                                     "Result", G_TYPE_STRING, &result,
514                                     "NumberReturned", G_TYPE_UINT, &number_returned,
515                                     "TotalMatches", G_TYPE_UINT, &total_matches,
516                                      NULL);
517
518     if (number_returned == 0)
519         return;
520
521     if (number_returned == 1) {
522         found = strstr (result, "id=\"");       
523         found += 4;
524         strncpy (subid, found, 32); subid[32] = '\0';
525
526         gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c,
527                                           "ObjectID", G_TYPE_STRING, subid,
528                                           "BrowseFlag", G_TYPE_STRING, "BrowseDirectChildren",
529                                           "Filter", G_TYPE_STRING, "@childCount",
530                                           "StartingIndex", G_TYPE_UINT, 0,
531                                           "RequestedCount", G_TYPE_UINT, 64,
532                                           "SortCriteria", G_TYPE_STRING, "",
533                                            NULL);
534         return;
535     }
536
537     if (number_returned > 1) {
538         dev_ctx_c->content_res = result;
539         dev_ctx_c->content_num = number_returned;
540     }
541 }
542
543 STATIC void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
544                                 gpointer data) {
545
546     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
547     GError *error;
548     char *result;
549
550     gupnp_service_proxy_end_action (content_dir, action, &error,
551                                     "Result", G_TYPE_STRING, &result,
552                                      NULL);
553
554     dev_ctx_c->content_res = result;
555 }
556
557 STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
558                               gpointer data)
559 {
560
561     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
562     GUPnPServiceProxy *av_transport_proxy;
563     GError *error;
564     char *time;
565     struct timeval tv_start, tv_now;
566
567     av_transport_proxy = GUPNP_SERVICE_PROXY (av_transport);
568
569     gupnp_service_proxy_end_action (av_transport, action, &error, NULL);
570
571     switch (dev_ctx_c->target_state) {
572         case PLAY:
573           gupnp_service_proxy_begin_action (av_transport_proxy, "Play", _rygel_do_cb, dev_ctx_c,
574                                            "InstanceID", G_TYPE_UINT, 0,
575                                            "Speed", G_TYPE_STRING, "1",
576                                             NULL);
577           break;
578        case PAUSE:
579           gupnp_service_proxy_begin_action (av_transport_proxy, "Pause", _rygel_do_cb, dev_ctx_c,
580                                            "InstanceID", G_TYPE_UINT, 0,
581                                             NULL);
582           break;
583        case STOP:
584           gupnp_service_proxy_begin_action (av_transport_proxy, "Stop", _rygel_do_cb, dev_ctx_c,
585                                            "InstanceID", G_TYPE_UINT, 0,
586                                             NULL);
587           break;
588        case SEEK:
589           time = _rygel_time_for_string (dev_ctx_c->action_args);
590           gupnp_service_proxy_begin_action (av_transport_proxy, "Seek", _rygel_do_cb, dev_ctx_c,
591                                            "InstanceID", G_TYPE_UINT, 0,
592                                            "Unit", G_TYPE_STRING, "ABS_TIME",
593                                            "Target", G_TYPE_STRING, time,
594                                             NULL);
595        default:
596          break;
597     }
598
599     gettimeofday (&tv_start, NULL);
600     gettimeofday (&tv_now, NULL);
601     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
602
603         g_main_context_iteration (dev_ctx_c->loop, FALSE);
604
605         if (dev_ctx_c->state == dev_ctx_c->target_state)
606             break;
607         gettimeofday (&tv_now, NULL);
608     }
609 }
610
611 STATIC void _rygel_upload_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
612                               gpointer data)
613 {
614     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
615     GUPnPServiceProxy *content_dir_proxy;
616     GError *error;
617     char *result, *start, *end, *dst_uri, *src_uri;
618     int length;
619     struct timeval tv_start, tv_now;
620
621     content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir);
622
623     if (!gupnp_service_proxy_end_action (content_dir, action, &error,
624                                          "Result", G_TYPE_STRING, &result,
625                                           NULL))
626       return;
627
628     start = strstr (result, "<res importUri=\"");
629     if (!start)
630       return;
631
632     start += 16;
633     end = strstr (start, "\"");
634     length = end - start;
635
636     dst_uri = (char*) malloc(length+1);
637     strncpy (dst_uri, start, length);
638     dst_uri[length] = '\0';
639
640     asprintf (&src_uri, "http://%s:%u%s", gupnp_context_get_host_ip (dev_ctx_c->context),
641                                           gupnp_context_get_port (dev_ctx_c->context),
642                                           dev_ctx_c->transfer_path);
643
644     /* host the file */
645     gupnp_context_host_path (dev_ctx_c->context, dev_ctx_c->transfer_path,
646                                                  dev_ctx_c->transfer_path);
647
648     gupnp_service_proxy_begin_action (content_dir_proxy, "ImportResource", _rygel_transfer_cb, dev_ctx_c,
649                                       "SourceURI", G_TYPE_STRING, src_uri,
650                                       "DestinationURI", G_TYPE_STRING, dst_uri,
651                                        NULL);
652
653     gettimeofday (&tv_start, NULL);
654     gettimeofday (&tv_now, NULL);
655     while (tv_now.tv_sec - tv_start.tv_sec <= 5) {
656
657         g_main_context_iteration (dev_ctx_c->loop, FALSE);
658
659         if (dev_ctx_c->transfer_started)
660             break;
661         gettimeofday (&tv_now, NULL);
662     }
663 }
664
665 STATIC void _rygel_transfer_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action,
666                                 gpointer data)
667 {
668     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
669     GError *error;
670     guint transfer_id;
671
672     if (!gupnp_service_proxy_end_action (content_dir, action, &error,
673                                          "TransferID", G_TYPE_UINT, &transfer_id,
674                                           NULL))
675       return;
676
677     dev_ctx_c->transfer_started = 1;
678 }
679
680 STATIC void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action,
681                           gpointer data)
682 {
683     dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data;
684     GError *error;
685
686     if (!gupnp_service_proxy_end_action (av_transport, action, &error,
687                                          NULL))
688       return;
689
690     dev_ctx_c->state = dev_ctx_c->target_state;
691 }