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