Update media-service-upnp to version 0.3.0 ( ca17a69 )
[profile/ivi/media-service-upnp.git] / src / device.c
1 /*
2  * media-service-upnp
3  *
4  * Copyright (C) 2012 Intel Corporation. All rights reserved.
5  *
6  * This program is free software; you can redistribute it and/or modify it
7  * under the terms and conditions of the GNU Lesser General Public License,
8  * version 2.1, as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope it will be useful, but WITHOUT
11  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
13  * for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
18  *
19  * Mark Ryan <mark.d.ryan@intel.com>
20  *
21  */
22
23 #include <string.h>
24 #include <libgupnp/gupnp-error.h>
25 #include <libsoup/soup.h>
26
27 #include "chain-task.h"
28 #include "device.h"
29 #include "error.h"
30 #include "interface.h"
31 #include "log.h"
32 #include "path.h"
33
34 #define MSU_SYSTEM_UPDATE_VAR "SystemUpdateID"
35 #define MSU_CONTAINER_UPDATE_VAR "ContainerUpdateIDs"
36 #define MEDIA_SERVER_DEVICE_TYPE "urn:schemas-upnp-org:device:MediaServer:"
37
38 #define MSU_UPLOAD_STATUS_IN_PROGRESS "IN_PROGRESS"
39 #define MSU_UPLOAD_STATUS_CANCELLED "CANCELLED"
40 #define MSU_UPLOAD_STATUS_ERROR "ERROR"
41 #define MSU_UPLOAD_STATUS_COMPLETED "COMPLETED"
42
43 typedef gboolean (*msu_device_count_cb_t)(msu_async_cb_data_t *cb_data,
44                                           gint count);
45
46 typedef struct msu_device_count_data_t_ msu_device_count_data_t;
47 struct msu_device_count_data_t_ {
48         msu_device_count_cb_t cb;
49         msu_async_cb_data_t *cb_data;
50 };
51
52 typedef struct msu_device_object_builder_t_ msu_device_object_builder_t;
53 struct msu_device_object_builder_t_ {
54         GVariantBuilder *vb;
55         gchar *id;
56         gboolean needs_child_count;
57 };
58
59 typedef struct msu_device_upload_job_t_ msu_device_upload_job_t;
60
61 typedef struct msu_device_upload_t_ msu_device_upload_t;
62 struct msu_device_upload_t_ {
63         SoupSession *soup_session;
64         SoupMessage *msg;
65         GMappedFile *mapped_file;
66         const gchar *status;
67         guint64 bytes_uploaded;
68         guint64 bytes_to_upload;
69 };
70
71 struct msu_device_upload_job_t_ {
72         gint upload_id;
73         msu_device_t *device;
74         guint remove_idle;
75 };
76
77 /* Private structure used in chain task */
78 typedef struct prv_new_device_ct_t_ prv_new_device_ct_t;
79 struct prv_new_device_ct_t_ {
80         msu_device_t *dev;
81         GDBusConnection *connection;
82         const GDBusSubtreeVTable *vtable;
83         void *user_data;
84         GHashTable *property_map;
85 };
86
87 static void prv_get_child_count(msu_async_cb_data_t *cb_data,
88                                 msu_device_count_cb_t cb, const gchar *id);
89 static void prv_retrieve_child_count_for_list(msu_async_cb_data_t *cb_data);
90 static void prv_container_update_cb(GUPnPServiceProxy *proxy,
91                                 const char *variable,
92                                 GValue *value,
93                                 gpointer user_data);
94 static void prv_system_update_cb(GUPnPServiceProxy *proxy,
95                                 const char *variable,
96                                 GValue *value,
97                                 gpointer user_data);
98 static void prv_msu_device_upload_delete(gpointer up);
99 static void prv_msu_upload_job_delete(gpointer up);
100 static void prv_get_sr_token_for_props(GUPnPServiceProxy *proxy,
101                              msu_device_t *device,
102                              GCancellable *cancellable,
103                              msu_async_cb_data_t *cb_data);
104
105 static void prv_msu_device_object_builder_delete(void *dob)
106 {
107         msu_device_object_builder_t *builder = dob;
108
109         if (builder) {
110                 if (builder->vb)
111                         g_variant_builder_unref(builder->vb);
112
113                 g_free(builder->id);
114                 g_free(builder);
115         }
116 }
117
118 static void prv_msu_device_count_data_new(msu_async_cb_data_t *cb_data,
119                                           msu_device_count_cb_t cb,
120                                           msu_device_count_data_t **count_data)
121 {
122         msu_device_count_data_t *cd;
123
124         cd = g_new(msu_device_count_data_t, 1);
125         cd->cb = cb;
126         cd->cb_data = cb_data;
127
128         *count_data = cd;
129 }
130
131 static void prv_msu_context_delete(gpointer context)
132 {
133         msu_device_context_t *ctx = context;
134
135         if (ctx) {
136                 if (ctx->timeout_id)
137                         (void) g_source_remove(ctx->timeout_id);
138
139                 if (ctx->subscribed) {
140                         gupnp_service_proxy_remove_notify(ctx->service_proxy,
141                                                 MSU_SYSTEM_UPDATE_VAR,
142                                                 prv_system_update_cb,
143                                                 ctx->device);
144                         gupnp_service_proxy_remove_notify(ctx->service_proxy,
145                                                 MSU_CONTAINER_UPDATE_VAR,
146                                                 prv_container_update_cb,
147                                                 ctx->device);
148                         gupnp_service_proxy_set_subscribed(ctx->service_proxy,
149                                                            FALSE);
150                 }
151
152                 if (ctx->device_proxy)
153                         g_object_unref(ctx->device_proxy);
154
155                 if (ctx->service_proxy)
156                         g_object_unref(ctx->service_proxy);
157
158                 g_free(ctx->ip_address);
159                 g_free(ctx);
160         }
161 }
162
163 static void prv_msu_context_new(const gchar *ip_address,
164                                 GUPnPDeviceProxy *proxy,
165                                 msu_device_t *device,
166                                 msu_device_context_t **context)
167 {
168         const gchar *service_type =
169                 "urn:schemas-upnp-org:service:ContentDirectory";
170         msu_device_context_t *ctx = g_new(msu_device_context_t, 1);
171
172         ctx->ip_address = g_strdup(ip_address);
173         ctx->device_proxy = proxy;
174         ctx->device = device;
175         g_object_ref(proxy);
176         ctx->service_proxy = (GUPnPServiceProxy *)
177                 gupnp_device_info_get_service((GUPnPDeviceInfo *) proxy,
178                                               service_type);
179         ctx->subscribed = FALSE;
180         ctx->timeout_id = 0;
181
182         *context = ctx;
183 }
184
185 void msu_device_delete(void *device)
186 {
187         msu_device_t *dev = device;
188
189         if (dev) {
190                 dev->shutting_down = TRUE;
191                 g_hash_table_unref(dev->upload_jobs);
192                 g_hash_table_unref(dev->uploads);
193
194                 if (dev->timeout_id)
195                         (void) g_source_remove(dev->timeout_id);
196
197                 if (dev->id)
198                         (void) g_dbus_connection_unregister_subtree(
199                                 dev->connection, dev->id);
200
201                 g_ptr_array_unref(dev->contexts);
202                 g_free(dev->path);
203                 g_variant_unref(dev->search_caps);
204                 g_variant_unref(dev->sort_caps);
205                 g_variant_unref(dev->sort_ext_caps);
206                 g_variant_unref(dev->feature_list);
207                 g_free(dev);
208         }
209 }
210
211 static void prv_build_container_update_array(const gchar *root_path,
212                                              const gchar *value,
213                                              GVariantBuilder *builder)
214 {
215         gchar **str_array;
216         int pos = 0;
217         gchar *path;
218
219         /*
220          * value contains (id, version) pairs
221          * we must extract ids only
222          */
223         str_array = g_strsplit(value, ",", 0);
224
225         while (str_array[pos]) {
226                 if ((pos % 2) == 0) {
227                         path = msu_path_from_id(root_path, str_array[pos]);
228                         g_variant_builder_add(builder, "o", path);
229                         g_free(path);
230                 }
231
232                 pos++;
233         }
234
235         g_strfreev(str_array);
236 }
237
238 static void prv_container_update_cb(GUPnPServiceProxy *proxy,
239                                     const char *variable,
240                                     GValue *value,
241                                     gpointer user_data)
242 {
243         msu_device_t *device = user_data;
244         GVariantBuilder array;
245
246         MSU_LOG_DEBUG("Container Update %s", g_value_get_string(value));
247
248         g_variant_builder_init(&array, G_VARIANT_TYPE("ao"));
249         prv_build_container_update_array(device->path,
250                                         g_value_get_string(value),
251                                         &array);
252
253         (void) g_dbus_connection_emit_signal(device->connection,
254                         NULL,
255                         device->path,
256                         MSU_INTERFACE_MEDIA_DEVICE,
257                         MSU_INTERFACE_CONTAINER_UPDATE,
258                         g_variant_new("(@ao)", g_variant_builder_end(&array)),
259                         NULL);
260 }
261
262 static void prv_system_update_cb(GUPnPServiceProxy *proxy,
263                                  const char *variable,
264                                  GValue *value,
265                                  gpointer user_data)
266 {
267         GVariantBuilder *array;
268         GVariant *val;
269         msu_device_t *device = user_data;
270         guint suid = g_value_get_uint(value);
271
272         MSU_LOG_DEBUG("System Update %u", suid);
273
274         device->system_update_id = suid;
275
276         array = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
277         g_variant_builder_add(array, "{sv}", MSU_INTERFACE_SYSTEM_UPDATE_ID,
278                               g_variant_new_uint32(suid));
279         val = g_variant_new("(s@a{sv}as)", MSU_INTERFACE_MEDIA_DEVICE,
280                             g_variant_builder_end(array),
281                             NULL);
282
283         (void) g_dbus_connection_emit_signal(device->connection,
284                                              NULL,
285                                              device->path,
286                                              MSU_INTERFACE_PROPERTIES,
287                                              MSU_INTERFACE_PROPERTIES_CHANGED,
288                                              val,
289                                              NULL);
290
291         g_variant_builder_unref(array);
292 }
293
294 static gboolean prv_re_enable_subscription(gpointer user_data)
295 {
296         msu_device_context_t *context = user_data;
297
298         context->timeout_id = 0;
299
300         return FALSE;
301 }
302
303 static void prv_subscription_lost_cb(GUPnPServiceProxy *proxy,
304                                      const GError *reason,
305                                      gpointer user_data)
306 {
307         msu_device_context_t *context = user_data;
308
309         if (!context->timeout_id) {
310                 gupnp_service_proxy_set_subscribed(context->service_proxy,
311                                                                         TRUE);
312                 context->timeout_id = g_timeout_add_seconds(10,
313                                                 prv_re_enable_subscription,
314                                                 context);
315         } else {
316                 g_source_remove(context->timeout_id);
317                 gupnp_service_proxy_remove_notify(context->service_proxy,
318                                                   MSU_SYSTEM_UPDATE_VAR,
319                                                   prv_system_update_cb,
320                                                   context->device);
321                 gupnp_service_proxy_remove_notify(context->service_proxy,
322                                                   MSU_CONTAINER_UPDATE_VAR,
323                                                   prv_container_update_cb,
324                                                   context->device);
325
326                 context->timeout_id = 0;
327                 context->subscribed = FALSE;
328         }
329 }
330
331 void msu_device_subscribe_to_contents_change(msu_device_t *device)
332 {
333         msu_device_context_t *context;
334
335         context = msu_device_get_context(device, NULL);
336
337         MSU_LOG_DEBUG("Subscribe for events on context: %s",
338                       context->ip_address);
339
340         gupnp_service_proxy_add_notify(context->service_proxy,
341                                 MSU_SYSTEM_UPDATE_VAR,
342                                 G_TYPE_UINT,
343                                 prv_system_update_cb,
344                                 device);
345
346         gupnp_service_proxy_add_notify(context->service_proxy,
347                                 MSU_CONTAINER_UPDATE_VAR,
348                                 G_TYPE_STRING,
349                                 prv_container_update_cb,
350                                 device);
351
352         context->subscribed = TRUE;
353         gupnp_service_proxy_set_subscribed(context->service_proxy, TRUE);
354
355         g_signal_connect(context->service_proxy,
356                                 "subscription-lost",
357                                 G_CALLBACK(prv_subscription_lost_cb),
358                                 context);
359 }
360
361 static void prv_feature_list_add_feature(gchar* root_path,
362                                          GUPnPFeature *feature,
363                                          GVariantBuilder *vb)
364 {
365         GVariantBuilder vbo;
366         GVariant *var_obj;
367         const char *name;
368         const char *version;
369         const char *obj_id;
370         gchar **obj;
371         gchar **saved;
372         gchar *path;
373
374         name = gupnp_feature_get_name(feature);
375         version = gupnp_feature_get_version(feature);
376         obj_id = gupnp_feature_get_object_ids(feature);
377
378         g_variant_builder_init(&vbo, G_VARIANT_TYPE("ao"));
379
380         if (obj_id != NULL && *obj_id) {
381                 obj = g_strsplit(obj_id, ",", 0);
382                 saved = obj;
383
384                 while (obj && *obj) {
385                         path = msu_path_from_id(root_path, *obj);
386                         g_variant_builder_add(&vbo, "o", path);
387                         g_free(path);
388                         obj++;
389                 }
390
391                 g_strfreev(saved);
392         }
393
394         var_obj = g_variant_builder_end(&vbo);
395         g_variant_builder_add(vb, "(ss@ao)", name, version, var_obj);
396 }
397
398 static void prv_get_feature_list_analyze(msu_device_t *device, gchar *result)
399 {
400         GUPnPFeatureListParser *parser;
401         GUPnPFeature *feature;
402         GList *list;
403         GList *item;
404         GError *error = NULL;
405         GVariantBuilder vb;
406 #if MSU_LOG_LEVEL & MSU_LOG_LEVEL_DEBUG
407         char *str;
408 #endif
409         parser = gupnp_feature_list_parser_new();
410         list = gupnp_feature_list_parser_parse_text(parser, result, &error);
411
412         if (error != NULL) {
413                 MSU_LOG_WARNING("GetFeatureList parsing failed: %s",
414                                 error->message);
415                 goto on_exit;
416         }
417
418         g_variant_builder_init(&vb, G_VARIANT_TYPE("a(ssao)"));
419         item = list;
420
421         while (item != NULL) {
422                 feature = (GUPnPFeature *) item->data;
423                 prv_feature_list_add_feature(device->path, feature, &vb);
424                 g_object_unref(feature);
425                 item = g_list_next(item);
426         }
427
428         device->feature_list = g_variant_ref_sink(g_variant_builder_end(&vb));
429
430 #if MSU_LOG_LEVEL & MSU_LOG_LEVEL_DEBUG
431         str = g_variant_print(device->feature_list, FALSE);
432         MSU_LOG_DEBUG("%s = %s", MSU_INTERFACE_PROP_SV_FEATURE_LIST, str);
433         g_free(str);
434 #endif
435
436 on_exit:
437         g_list_free(list);
438         g_object_unref(parser);
439
440         if (error != NULL)
441                 g_error_free(error);
442 }
443
444 static void prv_get_feature_list_cb(GUPnPServiceProxy *proxy,
445                                     GUPnPServiceProxyAction *action,
446                                     gpointer user_data)
447 {
448         gchar *result = NULL;
449         GError *error = NULL;
450         prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *) user_data;
451
452         if (!gupnp_service_proxy_end_action(proxy, action, &error,
453                                             "FeatureList", G_TYPE_STRING,
454                                             &result, NULL)) {
455                 MSU_LOG_WARNING("GetFeatureList operation failed: %s",
456                                 error->message);
457                 goto on_error;
458         }
459
460         MSU_LOG_DEBUG("GetFeatureList result: %s", result);
461
462         prv_get_feature_list_analyze(priv_t->dev, result);
463
464 on_error:
465         if (error != NULL)
466                 g_error_free(error);
467
468         g_free(result);
469 }
470
471 static GUPnPServiceProxyAction *prv_get_feature_list(msu_chain_task_t *chain,
472                                                      gboolean *failed)
473 {
474         msu_device_t *device;
475         msu_device_context_t *context;
476
477         device = msu_chain_task_get_device(chain);
478         context = msu_device_get_context(device, NULL);
479         *failed = FALSE;
480
481         return gupnp_service_proxy_begin_action(context->service_proxy,
482                                                 "GetFeatureList",
483                                                 msu_chain_task_begin_action_cb,
484                                                 chain, NULL);
485 }
486
487 static void prv_get_sort_ext_capabilities_analyze(msu_device_t *device,
488                                                   gchar *result)
489 {
490         gchar **caps;
491         gchar **saved;
492 #if MSU_LOG_LEVEL & MSU_LOG_LEVEL_DEBUG
493         gchar *props;
494 #endif
495         GVariantBuilder sort_ext_caps_vb;
496
497         g_variant_builder_init(&sort_ext_caps_vb, G_VARIANT_TYPE("as"));
498
499         caps = g_strsplit(result, ",", 0);
500         saved = caps;
501
502         while (caps && *caps) {
503                 g_variant_builder_add(&sort_ext_caps_vb, "s", *caps);
504                 caps++;
505         }
506
507         g_strfreev(saved);
508
509         device->sort_ext_caps = g_variant_ref_sink(g_variant_builder_end(
510                                                         &sort_ext_caps_vb));
511
512 #if MSU_LOG_LEVEL & MSU_LOG_LEVEL_DEBUG
513         props = g_variant_print(device->sort_ext_caps, FALSE);
514         MSU_LOG_DEBUG("%s = %s", MSU_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES,
515                                  props);
516         g_free(props);
517 #endif
518 }
519
520 static void prv_get_sort_ext_capabilities_cb(GUPnPServiceProxy *proxy,
521                                            GUPnPServiceProxyAction *action,
522                                            gpointer user_data)
523 {
524         gchar *result = NULL;
525         GError *error = NULL;
526         prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *) user_data;
527
528         if (!gupnp_service_proxy_end_action(proxy, action, &error,
529                                             "SortExtensionCaps",
530                                             G_TYPE_STRING, &result, NULL)) {
531                 MSU_LOG_WARNING(
532                         "GetSortExtensionCapabilities operation failed: %s",
533                         error->message);
534                 goto on_error;
535         }
536
537         MSU_LOG_DEBUG("GetSortExtensionCapabilities result: %s", result);
538
539         prv_get_sort_ext_capabilities_analyze(priv_t->dev, result);
540
541 on_error:
542
543         if (error)
544                 g_error_free(error);
545
546         g_free(result);
547 }
548
549 static GUPnPServiceProxyAction *prv_get_sort_ext_capabilities(
550                                                         msu_chain_task_t *chain,
551                                                         gboolean *failed)
552 {
553         msu_device_t *device;
554         msu_device_context_t *context;
555
556         device = msu_chain_task_get_device(chain);
557         context = msu_device_get_context(device, NULL);
558         *failed = FALSE;
559
560         return gupnp_service_proxy_begin_action(context->service_proxy,
561                                                 "GetSortExtensionCapabilities",
562                                                 msu_chain_task_begin_action_cb,
563                                                 chain, NULL);
564 }
565
566 static void prv_get_capabilities_analyze(GHashTable *property_map,
567                                          gchar *result,
568                                          GVariant **variant)
569 {
570         gchar **caps;
571         gchar **saved;
572         gchar *prop_name;
573         GVariantBuilder caps_vb;
574
575         g_variant_builder_init(&caps_vb, G_VARIANT_TYPE("as"));
576
577         caps = g_strsplit(result, ",", 0);
578         saved = caps;
579
580         while (caps && *caps) {
581                 prop_name = g_hash_table_lookup(property_map, *caps);
582
583                 if (prop_name)
584                         g_variant_builder_add(&caps_vb, "s", prop_name);
585
586                 caps++;
587         }
588
589         g_strfreev(saved);
590
591         *variant = g_variant_ref_sink(g_variant_builder_end(&caps_vb));
592
593 #if MSU_LOG_LEVEL & MSU_LOG_LEVEL_DEBUG
594         prop_name = g_variant_print(*variant, FALSE);
595         MSU_LOG_DEBUG("%s = %s", "   Variant", prop_name);
596         g_free(prop_name);
597 #endif
598 }
599
600 static void prv_get_sort_capabilities_cb(GUPnPServiceProxy *proxy,
601                                            GUPnPServiceProxyAction *action,
602                                            gpointer user_data)
603 {
604         gchar *result = NULL;
605         GError *error = NULL;
606         prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *) user_data;
607
608         if (!gupnp_service_proxy_end_action(proxy, action, &error, "SortCaps",
609                                             G_TYPE_STRING, &result, NULL)) {
610                 MSU_LOG_WARNING("GetSortCapabilities operation failed: %s",
611                                 error->message);
612                 goto on_error;
613         }
614
615         MSU_LOG_DEBUG("GetSortCapabilities result: %s", result);
616
617         prv_get_capabilities_analyze(priv_t->property_map, result,
618                                      &priv_t->dev->sort_caps);
619
620 on_error:
621
622         if (error)
623                 g_error_free(error);
624
625         g_free(result);
626 }
627
628 static GUPnPServiceProxyAction *prv_get_sort_capabilities(
629                                         msu_chain_task_t *chain,
630                                         gboolean *failed)
631 {
632         msu_device_t *device;
633         msu_device_context_t *context;
634
635         device = msu_chain_task_get_device(chain);
636         context = msu_device_get_context(device, NULL);
637         *failed = FALSE;
638
639         return gupnp_service_proxy_begin_action(context->service_proxy,
640                                                 "GetSortCapabilities",
641                                                 msu_chain_task_begin_action_cb,
642                                                 chain, NULL);
643 }
644
645 static void prv_get_search_capabilities_cb(GUPnPServiceProxy *proxy,
646                                            GUPnPServiceProxyAction *action,
647                                            gpointer user_data)
648 {
649         gchar *result = NULL;
650         GError *error = NULL;
651         prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *) user_data;
652
653         if (!gupnp_service_proxy_end_action(proxy, action, &error, "SearchCaps",
654                                             G_TYPE_STRING, &result, NULL)) {
655                 MSU_LOG_WARNING("GetSearchCapabilities operation failed: %s",
656                                 error->message);
657                 goto on_error;
658         }
659
660         MSU_LOG_DEBUG("GetSearchCapabilities result: %s", result);
661
662         prv_get_capabilities_analyze(priv_t->property_map, result,
663                                      &priv_t->dev->search_caps);
664
665 on_error:
666
667         if (error)
668                 g_error_free(error);
669
670         g_free(result);
671 }
672
673 static GUPnPServiceProxyAction *prv_get_search_capabilities(
674                                         msu_chain_task_t *chain,
675                                         gboolean *failed)
676 {
677         msu_device_t *device;
678         msu_device_context_t *context;
679
680         device = msu_chain_task_get_device(chain);
681         context = msu_device_get_context(device, NULL);
682         *failed = FALSE;
683
684         return gupnp_service_proxy_begin_action(context->service_proxy,
685                                                 "GetSearchCapabilities",
686                                                 msu_chain_task_begin_action_cb,
687                                                 chain, NULL);
688 }
689
690 static GUPnPServiceProxyAction *prv_subscribe(msu_chain_task_t *chain,
691                                               gboolean *failed)
692 {
693         msu_device_t *device;
694
695         device = msu_chain_task_get_device(chain);
696         msu_device_subscribe_to_contents_change(device);
697
698         *failed = FALSE;
699
700         return NULL;
701 }
702
703 static GUPnPServiceProxyAction *prv_declare(msu_chain_task_t *chain,
704                                             gboolean *failed)
705 {
706         guint flags;
707         guint id;
708         msu_device_t *device;
709         prv_new_device_ct_t *priv_t;
710
711         device = msu_chain_task_get_device(chain);
712
713         priv_t = (prv_new_device_ct_t *) msu_chain_task_get_user_data(chain);
714
715         flags = G_DBUS_SUBTREE_FLAGS_DISPATCH_TO_UNENUMERATED_NODES;
716         id =  g_dbus_connection_register_subtree(priv_t->connection,
717                                                  device->path,
718                                                  priv_t->vtable,
719                                                  flags,
720                                                  priv_t->user_data,
721                                                  NULL, NULL);
722         if (id) {
723                 device->id = id;
724
725                 device->uploads = g_hash_table_new_full(g_int_hash, g_int_equal,
726                                                 g_free,
727                                                 prv_msu_device_upload_delete);
728                 device->upload_jobs =
729                         g_hash_table_new_full(g_int_hash, g_int_equal,
730                                               g_free,
731                                               prv_msu_upload_job_delete);
732
733         } else
734                 MSU_LOG_ERROR("g_dbus_connection_register_subtree FAILED");
735
736         *failed = (!id);
737
738         return NULL;
739 }
740
741 msu_device_t *msu_device_new(GDBusConnection *connection,
742                              GUPnPDeviceProxy *proxy,
743                              const gchar *ip_address,
744                              const GDBusSubtreeVTable *vtable,
745                              void *user_data,
746                              GHashTable *property_map,
747                              guint counter,
748                              msu_chain_task_t *chain)
749 {
750         msu_device_t *dev;
751         prv_new_device_ct_t *priv_t;
752         gchar *new_path;
753
754         MSU_LOG_DEBUG("New Device on %s", ip_address);
755
756         new_path = g_strdup_printf("%s/%u", MSU_SERVER_PATH, counter);
757         MSU_LOG_DEBUG("Server Path %s", new_path);
758
759         dev = g_new0(msu_device_t, 1);
760         priv_t = g_new0(prv_new_device_ct_t, 1);
761
762         dev->connection = connection;
763         dev->contexts = g_ptr_array_new_with_free_func(prv_msu_context_delete);
764         dev->path = new_path;
765
766         priv_t->dev = dev;
767         priv_t->connection = connection;
768         priv_t->vtable = vtable;
769         priv_t->user_data = user_data;
770         priv_t->property_map = property_map;
771
772         msu_device_append_new_context(dev, ip_address, proxy);
773
774         msu_chain_task_add(chain, prv_get_search_capabilities, dev,
775                            prv_get_search_capabilities_cb, NULL, priv_t);
776
777         msu_chain_task_add(chain, prv_get_sort_capabilities, dev,
778                            prv_get_sort_capabilities_cb, NULL, priv_t);
779
780         msu_chain_task_add(chain, prv_get_sort_ext_capabilities, dev,
781                            prv_get_sort_ext_capabilities_cb, NULL, priv_t);
782
783         msu_chain_task_add(chain, prv_get_feature_list, dev,
784                            prv_get_feature_list_cb, NULL, priv_t);
785 MSU_LOG_DEBUG("AFTER");
786         msu_chain_task_add(chain, prv_subscribe, dev, NULL, NULL, NULL);
787         msu_chain_task_add(chain, prv_declare, dev, NULL, g_free, priv_t);
788
789         msu_chain_task_start(chain);
790
791         return dev;
792 }
793
794 void msu_device_append_new_context(msu_device_t *device,
795                                    const gchar *ip_address,
796                                    GUPnPDeviceProxy *proxy)
797 {
798         msu_device_context_t *context;
799
800         prv_msu_context_new(ip_address, proxy, device, &context);
801         g_ptr_array_add(device->contexts, context);
802 }
803
804 msu_device_t *msu_device_from_path(const gchar *path, GHashTable *device_list)
805 {
806         GHashTableIter iter;
807         gpointer value;
808         msu_device_t *device;
809         msu_device_t *retval = NULL;
810
811         g_hash_table_iter_init(&iter, device_list);
812
813         while (g_hash_table_iter_next(&iter, NULL, &value)) {
814                 device = value;
815
816                 if (!strcmp(device->path, path)) {
817                         retval = device;
818                         break;
819                 }
820         }
821
822         return retval;
823 }
824
825 msu_device_context_t *msu_device_get_context(msu_device_t *device,
826                                              msu_client_t *client)
827 {
828         msu_device_context_t *context;
829         unsigned int i;
830         const char ip4_local_prefix[] = "127.0.0.";
831         gboolean prefer_local;
832         gboolean is_local;
833
834         prefer_local = (client && client->prefer_local_addresses);
835
836         for (i = 0; i < device->contexts->len; ++i) {
837                 context = g_ptr_array_index(device->contexts, i);
838
839                 is_local = (!strncmp(context->ip_address, ip4_local_prefix,
840                                                 sizeof(ip4_local_prefix) - 1) ||
841                             !strcmp(context->ip_address, "::1") ||
842                             !strcmp(context->ip_address, "0:0:0:0:0:0:0:1"));
843
844                 if (prefer_local == is_local)
845                         break;
846         }
847
848         if (i == device->contexts->len)
849                 context = g_ptr_array_index(device->contexts, 0);
850
851         return context;
852 }
853
854 static void prv_found_child(GUPnPDIDLLiteParser *parser,
855                             GUPnPDIDLLiteObject *object,
856                             gpointer user_data)
857 {
858         msu_async_cb_data_t *cb_data = user_data;
859         msu_task_t *task = cb_data->task;
860         msu_task_get_children_t *task_data = &task->ut.get_children;
861         msu_async_bas_t *cb_task_data = &cb_data->ut.bas;
862         msu_device_object_builder_t *builder;
863         gboolean have_child_count;
864
865         MSU_LOG_DEBUG("Enter");
866
867         builder = g_new0(msu_device_object_builder_t, 1);
868
869         if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) {
870                 if (!task_data->containers)
871                         goto on_error;
872         } else {
873                 if (!task_data->items)
874                         goto on_error;
875         }
876
877         builder->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
878
879         if (!msu_props_add_object(builder->vb, object, cb_task_data->root_path,
880                                   task->path, cb_task_data->filter_mask))
881                 goto on_error;
882
883         if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) {
884                 msu_props_add_container(builder->vb,
885                                         (GUPnPDIDLLiteContainer *) object,
886                                         cb_task_data->filter_mask,
887                                         &have_child_count);
888
889                 if (!have_child_count && (cb_task_data->filter_mask &
890                                           MSU_UPNP_MASK_PROP_CHILD_COUNT)) {
891                         builder->needs_child_count = TRUE;
892                         builder->id = g_strdup(
893                                 gupnp_didl_lite_object_get_id(object));
894                         cb_task_data->need_child_count = TRUE;
895                 }
896         } else {
897                 msu_props_add_item(builder->vb, object,
898                                    cb_task_data->root_path,
899                                    cb_task_data->filter_mask,
900                                    cb_task_data->protocol_info);
901         }
902
903         g_ptr_array_add(cb_task_data->vbs, builder);
904
905         MSU_LOG_DEBUG("Exit with SUCCESS");
906
907         return;
908
909 on_error:
910
911         prv_msu_device_object_builder_delete(builder);
912
913         MSU_LOG_DEBUG("Exit with FAIL");
914 }
915
916 static GVariant *prv_children_result_to_variant(msu_async_cb_data_t *cb_data)
917 {
918         guint i;
919         msu_device_object_builder_t *builder;
920         msu_async_bas_t *cb_task_data = &cb_data->ut.bas;
921         GVariantBuilder vb;
922
923         g_variant_builder_init(&vb, G_VARIANT_TYPE("aa{sv}"));
924
925         for (i = 0; i < cb_task_data->vbs->len; ++i) {
926                 builder = g_ptr_array_index(cb_task_data->vbs, i);
927                 g_variant_builder_add(&vb, "@a{sv}",
928                                       g_variant_builder_end(builder->vb));
929         }
930
931         return  g_variant_builder_end(&vb);
932 }
933
934 static void prv_get_search_ex_result(msu_async_cb_data_t *cb_data)
935 {
936         GVariant *out_params[2];
937         msu_async_bas_t *cb_task_data = &cb_data->ut.bas;
938
939         out_params[0] = prv_children_result_to_variant(cb_data);
940         out_params[1] = g_variant_new_uint32(cb_task_data->max_count);
941
942         cb_data->result = g_variant_ref_sink(
943                 g_variant_new_tuple(out_params, 2));
944 }
945
946 static void prv_get_children_result(msu_async_cb_data_t *cb_data)
947 {
948         GVariant *retval = prv_children_result_to_variant(cb_data);
949         cb_data->result =  g_variant_ref_sink(retval);
950 }
951
952 static gboolean prv_child_count_for_list_cb(msu_async_cb_data_t *cb_data,
953                                             gint count)
954 {
955         msu_async_bas_t *cb_task_data = &cb_data->ut.bas;
956         msu_device_object_builder_t *builder;
957
958         builder = g_ptr_array_index(cb_task_data->vbs, cb_task_data->retrieved);
959         msu_props_add_child_count(builder->vb, count);
960         cb_task_data->retrieved++;
961         prv_retrieve_child_count_for_list(cb_data);
962
963         return cb_task_data->retrieved >= cb_task_data->vbs->len;
964 }
965
966 static void prv_retrieve_child_count_for_list(msu_async_cb_data_t *cb_data)
967 {
968         msu_async_bas_t *cb_task_data = &cb_data->ut.bas;
969         msu_device_object_builder_t *builder;
970         guint i;
971
972         for (i = cb_task_data->retrieved; i < cb_task_data->vbs->len; ++i) {
973                 builder = g_ptr_array_index(cb_task_data->vbs, i);
974
975                 if (builder->needs_child_count)
976                         break;
977         }
978
979         cb_task_data->retrieved = i;
980
981         if (i < cb_task_data->vbs->len)
982                 prv_get_child_count(cb_data, prv_child_count_for_list_cb,
983                                     builder->id);
984         else
985                 cb_task_data->get_children_cb(cb_data);
986 }
987
988 static void prv_get_children_cb(GUPnPServiceProxy *proxy,
989                                 GUPnPServiceProxyAction *action,
990                                 gpointer user_data)
991 {
992         gchar *result = NULL;
993         GUPnPDIDLLiteParser *parser = NULL;
994         GError *upnp_error = NULL;
995         msu_async_cb_data_t *cb_data = user_data;
996         msu_async_bas_t *cb_task_data = &cb_data->ut.bas;
997
998         MSU_LOG_DEBUG("Enter");
999
1000         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
1001                                             &upnp_error,
1002                                             "Result", G_TYPE_STRING,
1003                                             &result, NULL)) {
1004                 MSU_LOG_WARNING("Browse operation failed: %s",
1005                               upnp_error->message);
1006
1007                 cb_data->error = g_error_new(MSU_ERROR,
1008                                              MSU_ERROR_OPERATION_FAILED,
1009                                              "Browse operation failed: %s",
1010                                              upnp_error->message);
1011                 goto on_error;
1012         }
1013
1014         MSU_LOG_DEBUG("GetChildren result: %s", result);
1015
1016         parser = gupnp_didl_lite_parser_new();
1017
1018         g_signal_connect(parser, "object-available" ,
1019                          G_CALLBACK(prv_found_child), cb_data);
1020
1021         cb_task_data->vbs = g_ptr_array_new_with_free_func(
1022                 prv_msu_device_object_builder_delete);
1023
1024         if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error)
1025                 && upnp_error->code != GUPNP_XML_ERROR_EMPTY_NODE) {
1026                 MSU_LOG_WARNING("Unable to parse results of browse: %s",
1027                               upnp_error->message);
1028
1029                 cb_data->error = g_error_new(MSU_ERROR,
1030                                              MSU_ERROR_OPERATION_FAILED,
1031                                              "Unable to parse results of "
1032                                              "browse: %s", upnp_error->message);
1033                 goto on_error;
1034         }
1035
1036         if (cb_task_data->need_child_count) {
1037                 MSU_LOG_DEBUG("Need to retrieve ChildCounts");
1038
1039                 cb_task_data->get_children_cb = prv_get_children_result;
1040                 prv_retrieve_child_count_for_list(cb_data);
1041                 goto no_complete;
1042         } else {
1043                 prv_get_children_result(cb_data);
1044         }
1045
1046 on_error:
1047
1048         (void) g_idle_add(msu_async_complete_task, cb_data);
1049         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
1050
1051 no_complete:
1052
1053         if (upnp_error)
1054                 g_error_free(upnp_error);
1055
1056         if (parser)
1057                 g_object_unref(parser);
1058
1059         g_free(result);
1060
1061         MSU_LOG_DEBUG("Exit");
1062 }
1063
1064 void msu_device_get_children(msu_device_t *device, msu_client_t *client,
1065                              msu_task_t *task, msu_async_cb_data_t *cb_data,
1066                              const gchar *upnp_filter, const gchar *sort_by,
1067                              GCancellable *cancellable)
1068 {
1069         msu_device_context_t *context;
1070
1071         MSU_LOG_DEBUG("Enter");
1072
1073         context = msu_device_get_context(device, client);
1074
1075         cb_data->action =
1076                 gupnp_service_proxy_begin_action(context->service_proxy,
1077                                                  "Browse",
1078                                                  prv_get_children_cb,
1079                                                  cb_data,
1080                                                  "ObjectID", G_TYPE_STRING,
1081                                                  cb_data->id,
1082
1083                                                  "BrowseFlag", G_TYPE_STRING,
1084                                                  "BrowseDirectChildren",
1085
1086                                                  "Filter", G_TYPE_STRING,
1087                                                  upnp_filter,
1088
1089                                                  "StartingIndex", G_TYPE_INT,
1090                                                  task->ut.get_children.start,
1091                                                  "RequestedCount", G_TYPE_INT,
1092                                                  task->ut.get_children.count,
1093                                                  "SortCriteria", G_TYPE_STRING,
1094                                                  sort_by,
1095                                                  NULL);
1096         cb_data->proxy = context->service_proxy;
1097
1098         cb_data->cancel_id =
1099                 g_cancellable_connect(cancellable,
1100                                       G_CALLBACK(msu_async_task_cancelled),
1101                                       cb_data, NULL);
1102         cb_data->cancellable = cancellable;
1103
1104         MSU_LOG_DEBUG("Exit");
1105 }
1106
1107 static void prv_get_item(GUPnPDIDLLiteParser *parser,
1108                          GUPnPDIDLLiteObject *object,
1109                          gpointer user_data)
1110 {
1111         msu_async_cb_data_t *cb_data = user_data;
1112         msu_async_get_all_t *cb_task_data = &cb_data->ut.get_all;
1113
1114         if (!GUPNP_IS_DIDL_LITE_CONTAINER(object))
1115                 msu_props_add_item(cb_task_data->vb, object,
1116                                    cb_task_data->root_path,
1117                                    0xffffffff,
1118                                    cb_task_data->protocol_info);
1119         else
1120                 cb_data->error = g_error_new(MSU_ERROR,
1121                                              MSU_ERROR_UNKNOWN_INTERFACE,
1122                                              "Interface not supported on "
1123                                              "container.");
1124 }
1125
1126 static void prv_get_container(GUPnPDIDLLiteParser *parser,
1127                        GUPnPDIDLLiteObject *object,
1128                        gpointer user_data)
1129 {
1130         msu_async_cb_data_t *cb_data = user_data;
1131         msu_async_get_all_t *cb_task_data = &cb_data->ut.get_all;
1132         gboolean have_child_count;
1133
1134         if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) {
1135                 msu_props_add_container(cb_task_data->vb,
1136                                         (GUPnPDIDLLiteContainer *) object,
1137                                         0xffffffff,
1138                                         &have_child_count);
1139                 if (!have_child_count)
1140                         cb_task_data->need_child_count = TRUE;
1141         } else {
1142                 cb_data->error = g_error_new(MSU_ERROR,
1143                                              MSU_ERROR_UNKNOWN_INTERFACE,
1144                                              "Interface not supported on "
1145                                              "item.");
1146         }
1147 }
1148
1149 static void prv_get_object(GUPnPDIDLLiteParser *parser,
1150                            GUPnPDIDLLiteObject *object,
1151                            gpointer user_data)
1152 {
1153         msu_async_cb_data_t *cb_data = user_data;
1154         msu_async_get_all_t *cb_task_data = &cb_data->ut.get_all;
1155         const char *id;
1156         const char *parent_path;
1157         gchar *path = NULL;
1158
1159         id = gupnp_didl_lite_object_get_parent_id(object);
1160
1161         if (!id || !strcmp(id, "-1") || !strcmp(id, "")) {
1162                 parent_path = cb_task_data->root_path;
1163         } else {
1164                 path = msu_path_from_id(cb_task_data->root_path, id);
1165                 parent_path = path;
1166         }
1167
1168         if (!msu_props_add_object(cb_task_data->vb, object,
1169                                   cb_task_data->root_path,
1170                                   parent_path, 0xffffffff))
1171                 cb_data->error = g_error_new(MSU_ERROR, MSU_ERROR_BAD_RESULT,
1172                                              "Unable to retrieve mandatory "
1173                                              " object properties");
1174         g_free(path);
1175 }
1176
1177 static void prv_get_all(GUPnPDIDLLiteParser *parser,
1178                         GUPnPDIDLLiteObject *object,
1179                         gpointer user_data)
1180 {
1181         msu_async_cb_data_t *cb_data = user_data;
1182         msu_async_get_all_t *cb_task_data = &cb_data->ut.get_all;
1183         gboolean have_child_count;
1184
1185         prv_get_object(parser, object, user_data);
1186
1187         if (!cb_data->error) {
1188                 if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) {
1189                         msu_props_add_container(
1190                                 cb_task_data->vb,
1191                                 (GUPnPDIDLLiteContainer *)
1192                                 object, 0xffffffff,
1193                                 &have_child_count);
1194                         if (!have_child_count)
1195                                 cb_task_data->need_child_count = TRUE;
1196                 } else {
1197                         msu_props_add_item(cb_task_data->vb,
1198                                            object,
1199                                            cb_task_data->root_path,
1200                                            0xffffffff,
1201                                            cb_task_data->protocol_info);
1202                 }
1203         }
1204 }
1205
1206 static gboolean prv_device_subscribed(msu_device_t *device)
1207 {
1208         msu_device_context_t *context;
1209         unsigned int i;
1210         gboolean subscribed = FALSE;
1211
1212         for (i = 0; i < device->contexts->len; ++i) {
1213                 context = g_ptr_array_index(device->contexts, i);
1214                 if (context->subscribed) {
1215                         subscribed = TRUE;
1216                         break;
1217                 }
1218         }
1219
1220         return subscribed;
1221 }
1222
1223 static void prv_system_update_id_for_prop_cb(GUPnPServiceProxy *proxy,
1224                                     GUPnPServiceProxyAction *action,
1225                                     gpointer user_data)
1226 {
1227         GError *upnp_error = NULL;
1228         guint id;
1229         msu_async_cb_data_t *cb_data = user_data;
1230
1231         MSU_LOG_DEBUG("Enter");
1232
1233         if (!gupnp_service_proxy_end_action(proxy, action, &upnp_error,
1234                                             "Id", G_TYPE_UINT,
1235                                             &id,
1236                                             NULL)) {
1237                 MSU_LOG_ERROR("Unable to retrieve ServiceUpdateID: %s %s",
1238                                g_quark_to_string(upnp_error->domain),
1239                                upnp_error->message);
1240
1241                 cb_data->error = g_error_new(MSU_ERROR,
1242                                 MSU_ERROR_OPERATION_FAILED,
1243                                 "Unable to retrieve ServiceUpdateID: %s",
1244                                 upnp_error->message);
1245
1246                 goto on_complete;
1247         }
1248
1249         cb_data->result = g_variant_ref_sink(g_variant_new_uint32(id));
1250
1251 on_complete:
1252
1253         (void) g_idle_add(msu_async_complete_task, cb_data);
1254         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
1255
1256         if (upnp_error)
1257                 g_error_free(upnp_error);
1258
1259         MSU_LOG_DEBUG("Exit");
1260 }
1261
1262 static void prv_get_system_update_id_for_prop(GUPnPServiceProxy *proxy,
1263                                      msu_device_t *device,
1264                                      GCancellable *cancellable,
1265                                      msu_async_cb_data_t *cb_data)
1266 {
1267         guint suid;
1268
1269         MSU_LOG_DEBUG("Enter");
1270
1271         if (prv_device_subscribed(device)) {
1272                 suid = device->system_update_id;
1273
1274                 cb_data->result = g_variant_ref_sink(
1275                                         g_variant_new_uint32(suid));
1276
1277                 (void) g_idle_add(msu_async_complete_task, cb_data);
1278
1279                 goto on_complete;
1280         }
1281
1282         gupnp_service_proxy_begin_action(proxy, "GetSystemUpdateID",
1283                                          prv_system_update_id_for_prop_cb,
1284                                          cb_data,
1285                                          NULL);
1286
1287         cb_data->proxy = proxy;
1288
1289         cb_data->cancel_id =
1290         g_cancellable_connect(cancellable,
1291                                       G_CALLBACK(msu_async_task_cancelled),
1292                                       cb_data, NULL);
1293         cb_data->cancellable = cancellable;
1294
1295 on_complete:
1296
1297         MSU_LOG_DEBUG("Exit");
1298 }
1299
1300 static void prv_system_update_id_for_props_cb(GUPnPServiceProxy *proxy,
1301                                     GUPnPServiceProxyAction *action,
1302                                     gpointer user_data)
1303 {
1304         GError *upnp_error = NULL;
1305         guint id;
1306         msu_async_cb_data_t *cb_data = user_data;
1307         msu_async_get_all_t *cb_task_data = &cb_data->ut.get_all;
1308
1309         MSU_LOG_DEBUG("Enter");
1310
1311         if (!gupnp_service_proxy_end_action(proxy, action, &upnp_error,
1312                                             "Id", G_TYPE_UINT,
1313                                             &id,
1314                                             NULL)) {
1315                 MSU_LOG_ERROR("Unable to retrieve ServiceUpdateID: %s %s",
1316                                g_quark_to_string(upnp_error->domain),
1317                                upnp_error->message);
1318
1319                 cb_data->error = g_error_new(MSU_ERROR,
1320                                 MSU_ERROR_OPERATION_FAILED,
1321                                 "Unable to retrieve ServiceUpdateID: %s",
1322                                 upnp_error->message);
1323
1324                 goto on_complete;
1325         }
1326
1327         g_variant_builder_add(cb_task_data->vb, "{sv}",
1328                               MSU_SYSTEM_UPDATE_VAR,
1329                               g_variant_new_uint32(id));
1330
1331         cb_data->result = g_variant_ref_sink(g_variant_builder_end(
1332                                                 cb_task_data->vb));
1333
1334 on_complete:
1335
1336         if (!cb_data->error)
1337                 prv_get_sr_token_for_props(proxy, cb_task_data->device,
1338                                            cb_data->cancellable, cb_data);
1339         else {
1340                 (void) g_idle_add(msu_async_complete_task, cb_data);
1341                 g_cancellable_disconnect(cb_data->cancellable,
1342                                          cb_data->cancel_id);
1343         }
1344
1345         if (upnp_error)
1346                 g_error_free(upnp_error);
1347
1348         MSU_LOG_DEBUG("Exit");
1349 }
1350
1351 static void prv_get_system_update_id_for_props(GUPnPServiceProxy *proxy,
1352                                      msu_device_t *device,
1353                                      GCancellable *cancellable,
1354                                      msu_async_cb_data_t *cb_data)
1355 {
1356         msu_async_get_all_t *cb_task_data;
1357         guint suid;
1358
1359         MSU_LOG_DEBUG("Enter");
1360
1361         if (prv_device_subscribed(device)) {
1362                 suid = device->system_update_id;
1363
1364                 cb_task_data = &cb_data->ut.get_all;
1365
1366                 g_variant_builder_add(cb_task_data->vb, "{sv}",
1367                                       MSU_SYSTEM_UPDATE_VAR,
1368                                       g_variant_new_uint32(suid));
1369
1370                 prv_get_sr_token_for_props(proxy, device, cancellable, cb_data);
1371
1372                 goto on_complete;
1373         }
1374
1375         gupnp_service_proxy_begin_action(proxy, "GetSystemUpdateID",
1376                                          prv_system_update_id_for_props_cb,
1377                                          cb_data,
1378                                          NULL);
1379
1380         cb_data->proxy = proxy;
1381
1382         cb_data->cancel_id =
1383         g_cancellable_connect(cancellable,
1384                                       G_CALLBACK(msu_async_task_cancelled),
1385                                       cb_data, NULL);
1386         cb_data->cancellable = cancellable;
1387
1388 on_complete:
1389
1390         MSU_LOG_DEBUG("Exit");
1391 }
1392
1393 static int prv_get_media_server_version(msu_device_t *device)
1394 {
1395         msu_device_context_t *context;
1396         const char *device_type;
1397         const char *version;
1398
1399         context = msu_device_get_context(device, NULL);
1400         device_type = gupnp_device_info_get_device_type((GUPnPDeviceInfo *)
1401                                                         context->device_proxy);
1402
1403         if (strncmp(device_type, MEDIA_SERVER_DEVICE_TYPE,
1404                                         sizeof(MEDIA_SERVER_DEVICE_TYPE) - 1))
1405                 goto on_error;
1406
1407         version = device_type + sizeof(MEDIA_SERVER_DEVICE_TYPE) - 1;
1408
1409         return atoi(version);
1410
1411 on_error:
1412
1413         return -1;
1414 }
1415
1416 static void prv_service_reset_for_prop_cb(GUPnPServiceProxy *proxy,
1417                                           GUPnPServiceProxyAction *action,
1418                                           gpointer user_data)
1419 {
1420         GError *upnp_error = NULL;
1421         gchar *token = NULL;
1422         msu_async_cb_data_t *cb_data = user_data;
1423
1424         MSU_LOG_DEBUG("Enter");
1425
1426         if (!gupnp_service_proxy_end_action(proxy, action, &upnp_error,
1427                                             "ResetToken", G_TYPE_STRING,
1428                                             &token,
1429                                             NULL)) {
1430                 MSU_LOG_ERROR("Unable to retrieve ServiceResetToken: %s %s",
1431                                 g_quark_to_string(upnp_error->domain),
1432                                  upnp_error->message);
1433
1434
1435                 cb_data->error = g_error_new(MSU_ERROR,
1436                                              MSU_ERROR_OPERATION_FAILED,
1437                                              "GetServiceResetToken failed: %s",
1438                                              upnp_error->message);
1439
1440                 goto on_complete;
1441         }
1442
1443         cb_data->result = g_variant_ref_sink(g_variant_new_string(token));
1444
1445         g_free(token);
1446
1447         MSU_LOG_DEBUG("Service Reset %s", token);
1448
1449 on_complete:
1450
1451         (void) g_idle_add(msu_async_complete_task, cb_data);
1452         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
1453
1454         if (upnp_error)
1455                 g_error_free(upnp_error);
1456
1457         MSU_LOG_DEBUG("Exit");
1458 }
1459
1460 static void prv_get_sr_token_for_prop(GUPnPServiceProxy *proxy,
1461                              msu_device_t *device,
1462                              GCancellable *cancellable,
1463                              msu_async_cb_data_t *cb_data)
1464 {
1465         MSU_LOG_DEBUG("Enter");
1466
1467         if (prv_get_media_server_version(device) < 3) {
1468                 cb_data->error = g_error_new(MSU_ERROR,
1469                                              MSU_ERROR_UNKNOWN_PROPERTY,
1470                                              "Unknown property");
1471
1472                 (void) g_idle_add(msu_async_complete_task, cb_data);
1473
1474                 goto on_error;
1475         }
1476
1477         gupnp_service_proxy_begin_action(proxy, "GetServiceResetToken",
1478                                          prv_service_reset_for_prop_cb,
1479                                          cb_data,
1480                                          NULL);
1481
1482         cb_data->proxy = proxy;
1483
1484         cb_data->cancel_id = g_cancellable_connect(cancellable,
1485                                         G_CALLBACK(msu_async_task_cancelled),
1486                                         cb_data, NULL);
1487         cb_data->cancellable = cancellable;
1488
1489 on_error:
1490
1491         MSU_LOG_DEBUG("Exit");
1492 }
1493
1494 static void prv_service_reset_for_props_cb(GUPnPServiceProxy *proxy,
1495                                           GUPnPServiceProxyAction *action,
1496                                           gpointer user_data)
1497 {
1498         GError *upnp_error = NULL;
1499         gchar *token = NULL;
1500         msu_async_cb_data_t *cb_data = user_data;
1501         msu_async_get_all_t *cb_task_data;
1502
1503         MSU_LOG_DEBUG("Enter");
1504
1505         if (!gupnp_service_proxy_end_action(proxy, action, &upnp_error,
1506                                             "ResetToken", G_TYPE_STRING,
1507                                             &token,
1508                                             NULL)) {
1509                 MSU_LOG_ERROR("Unable to retrieve ServiceResetToken: %s %s",
1510                               g_quark_to_string(upnp_error->domain),
1511                               upnp_error->message);
1512
1513                 cb_data->error = g_error_new(MSU_ERROR,
1514                                              MSU_ERROR_OPERATION_FAILED,
1515                                              "GetServiceResetToken failed: %s",
1516                                              upnp_error->message);
1517
1518                 goto on_complete;
1519         }
1520
1521         cb_task_data = &cb_data->ut.get_all;
1522         g_variant_builder_add(cb_task_data->vb, "{sv}",
1523                               MSU_INTERFACE_PROP_ESV_SERVICE_RESET_TOKEN,
1524                               g_variant_new_string(token));
1525
1526         cb_data->result = g_variant_ref_sink(g_variant_builder_end(
1527                                                 cb_task_data->vb));
1528
1529         g_free(token);
1530
1531         MSU_LOG_DEBUG("Service Reset %s", token);
1532
1533 on_complete:
1534
1535         (void) g_idle_add(msu_async_complete_task, cb_data);
1536         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
1537
1538         if (upnp_error)
1539                 g_error_free(upnp_error);
1540
1541         MSU_LOG_DEBUG("Exit");
1542 }
1543
1544 static void prv_get_sr_token_for_props(GUPnPServiceProxy *proxy,
1545                              msu_device_t *device,
1546                              GCancellable *cancellable,
1547                              msu_async_cb_data_t *cb_data)
1548 {
1549         msu_async_get_all_t *cb_task_data;
1550
1551         MSU_LOG_DEBUG("Enter");
1552
1553         if (prv_get_media_server_version(device) < 3) {
1554                 cb_task_data = &cb_data->ut.get_all;
1555
1556                 cb_data->result = g_variant_ref_sink(g_variant_builder_end(
1557                                                         cb_task_data->vb));
1558
1559                 goto on_complete; /* No error here, just skip the property */
1560         }
1561
1562         gupnp_service_proxy_begin_action(proxy, "GetServiceResetToken",
1563                                          prv_service_reset_for_props_cb,
1564                                          cb_data,
1565                                          NULL);
1566
1567         cb_data->proxy = proxy;
1568
1569         cb_data->cancel_id = g_cancellable_connect(cancellable,
1570                                         G_CALLBACK(msu_async_task_cancelled),
1571                                         cb_data, NULL);
1572         cb_data->cancellable = cancellable;
1573
1574 on_complete:
1575
1576         (void) g_idle_add(msu_async_complete_task, cb_data);
1577
1578         MSU_LOG_DEBUG("Exit");
1579 }
1580
1581 static gboolean prv_get_all_child_count_cb(msu_async_cb_data_t *cb_data,
1582                                        gint count)
1583 {
1584         msu_async_get_all_t *cb_task_data = &cb_data->ut.get_all;
1585
1586         msu_props_add_child_count(cb_task_data->vb, count);
1587         if (cb_task_data->device_object)
1588                 prv_get_system_update_id_for_props(cb_data->proxy,
1589                                                    cb_task_data->device,
1590                                                    cb_data->cancellable,
1591                                                    cb_data);
1592         else
1593                 cb_data->result = g_variant_ref_sink(g_variant_builder_end(
1594                                                      cb_task_data->vb));
1595
1596         return !cb_task_data->device_object;
1597 }
1598
1599 static void prv_get_all_ms2spec_props_cb(GUPnPServiceProxy *proxy,
1600                                          GUPnPServiceProxyAction *action,
1601                                          gpointer user_data)
1602 {
1603         GError *upnp_error = NULL;
1604         gchar *result = NULL;
1605         GUPnPDIDLLiteParser *parser = NULL;
1606         msu_async_cb_data_t *cb_data = user_data;
1607         msu_async_get_all_t *cb_task_data = &cb_data->ut.get_all;
1608
1609         MSU_LOG_DEBUG("Enter");
1610
1611         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
1612                                             &upnp_error,
1613                                             "Result", G_TYPE_STRING,
1614                                             &result, NULL)) {
1615                 MSU_LOG_WARNING("Browse operation failed: %s",
1616                               upnp_error->message);
1617
1618                 cb_data->error = g_error_new(MSU_ERROR,
1619                                              MSU_ERROR_OPERATION_FAILED,
1620                                              "Browse operation failed: %s",
1621                                              upnp_error->message);
1622                 goto on_error;
1623         }
1624
1625         MSU_LOG_DEBUG("GetMS2SpecProps result: %s", result);
1626
1627         parser = gupnp_didl_lite_parser_new();
1628
1629         g_signal_connect(parser, "object-available" , cb_task_data->prop_func,
1630                          cb_data);
1631
1632         if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error)) {
1633                 if (upnp_error->code == GUPNP_XML_ERROR_EMPTY_NODE) {
1634                         MSU_LOG_WARNING("Property not defined for object");
1635
1636                         cb_data->error =
1637                                 g_error_new(MSU_ERROR,
1638                                             MSU_ERROR_UNKNOWN_PROPERTY,
1639                                             "Property not defined for object");
1640                 } else {
1641                         MSU_LOG_WARNING("Unable to parse results of browse: %s",
1642                                       upnp_error->message);
1643
1644                         cb_data->error =
1645                                 g_error_new(MSU_ERROR,
1646                                             MSU_ERROR_OPERATION_FAILED,
1647                                             "Unable to parse results of "
1648                                             "browse: %s",
1649                                             upnp_error->message);
1650                 }
1651                 goto on_error;
1652         }
1653
1654         if (cb_data->error)
1655                 goto on_error;
1656
1657         if (cb_task_data->need_child_count) {
1658                 MSU_LOG_DEBUG("Need Child Count");
1659
1660                 prv_get_child_count(cb_data, prv_get_all_child_count_cb,
1661                         cb_data->id);
1662
1663                 goto no_complete;
1664         } else if (cb_data->task->type == MSU_TASK_GET_ALL_PROPS &&
1665                                                 cb_task_data->device_object) {
1666                 prv_get_system_update_id_for_props(proxy, cb_task_data->device,
1667                                                 cb_data->cancellable, cb_data);
1668
1669                 goto no_complete;
1670         } else {
1671                 cb_data->result = g_variant_ref_sink(g_variant_builder_end(
1672                                                              cb_task_data->vb));
1673         }
1674
1675 on_error:
1676
1677         (void) g_idle_add(msu_async_complete_task, cb_data);
1678         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
1679
1680 no_complete:
1681
1682         if (upnp_error)
1683                 g_error_free(upnp_error);
1684
1685         if (parser)
1686                 g_object_unref(parser);
1687
1688         g_free(result);
1689
1690         MSU_LOG_DEBUG("Exit");
1691 }
1692
1693 static void prv_get_all_ms2spec_props(msu_device_context_t *context,
1694                                       GCancellable *cancellable,
1695                                       msu_async_cb_data_t *cb_data)
1696 {
1697         msu_async_get_all_t *cb_task_data = &cb_data->ut.get_all;
1698         msu_task_t *task = cb_data->task;
1699         msu_task_get_props_t *task_data = &task->ut.get_props;
1700
1701         MSU_LOG_DEBUG("Enter called");
1702
1703         if (!strcmp(MSU_INTERFACE_MEDIA_CONTAINER, task_data->interface_name))
1704                 cb_task_data->prop_func = G_CALLBACK(prv_get_container);
1705         else if (!strcmp(MSU_INTERFACE_MEDIA_ITEM, task_data->interface_name))
1706                 cb_task_data->prop_func = G_CALLBACK(prv_get_item);
1707         else if (!strcmp(MSU_INTERFACE_MEDIA_OBJECT, task_data->interface_name))
1708                 cb_task_data->prop_func = G_CALLBACK(prv_get_object);
1709         else  if (!strcmp("", task_data->interface_name))
1710                 cb_task_data->prop_func = G_CALLBACK(prv_get_all);
1711         else {
1712                 MSU_LOG_WARNING("Interface is unknown.");
1713
1714                 cb_data->error =
1715                         g_error_new(MSU_ERROR, MSU_ERROR_UNKNOWN_INTERFACE,
1716                                     "Interface is unknown.");
1717                 goto on_error;
1718         }
1719
1720         cb_data->action = gupnp_service_proxy_begin_action(
1721                 context->service_proxy, "Browse",
1722                 prv_get_all_ms2spec_props_cb, cb_data,
1723                 "ObjectID", G_TYPE_STRING, cb_data->id,
1724                 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
1725                 "Filter", G_TYPE_STRING, "*",
1726                 "StartingIndex", G_TYPE_INT, 0,
1727                 "RequestedCount", G_TYPE_INT, 0,
1728                 "SortCriteria", G_TYPE_STRING,
1729                 "", NULL);
1730
1731         cb_data->proxy = context->service_proxy;
1732
1733         cb_data->cancel_id =
1734                 g_cancellable_connect(cancellable,
1735                                       G_CALLBACK(msu_async_task_cancelled),
1736                                       cb_data, NULL);
1737         cb_data->cancellable = cancellable;
1738
1739         MSU_LOG_DEBUG("Exit with SUCCESS");
1740
1741         return;
1742
1743 on_error:
1744
1745         (void) g_idle_add(msu_async_complete_task, cb_data);
1746
1747         MSU_LOG_DEBUG("Exit with FAIL");
1748
1749         return;
1750 }
1751
1752 void msu_device_get_all_props(msu_device_t *device,  msu_client_t *client,
1753                               msu_task_t *task, msu_async_cb_data_t *cb_data,
1754                               gboolean root_object,
1755                               GCancellable *cancellable)
1756 {
1757         msu_async_get_all_t *cb_task_data;
1758         msu_task_get_props_t *task_data = &task->ut.get_props;
1759         msu_device_context_t *context;
1760
1761         MSU_LOG_DEBUG("Enter");
1762
1763         context = msu_device_get_context(device, client);
1764         cb_task_data = &cb_data->ut.get_all;
1765
1766         cb_task_data->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
1767         cb_task_data->device_object = root_object;
1768         cb_task_data->device = device;
1769
1770         if (!strcmp(task_data->interface_name, MSU_INTERFACE_MEDIA_DEVICE)) {
1771                 if (root_object) {
1772                         msu_props_add_device(
1773                                 (GUPnPDeviceInfo *) context->device_proxy,
1774                                 device,
1775                                 cb_task_data->vb);
1776
1777                         prv_get_system_update_id_for_props(
1778                                                         context->service_proxy,
1779                                                         device,
1780                                                         cancellable,
1781                                                         cb_data);
1782                 } else {
1783                         cb_data->error =
1784                                 g_error_new(MSU_ERROR,
1785                                             MSU_ERROR_UNKNOWN_INTERFACE,
1786                                             "Interface is only valid on "
1787                                             "root objects.");
1788
1789                         (void) g_idle_add(msu_async_complete_task, cb_data);
1790                 }
1791
1792         } else if (strcmp(task_data->interface_name, "")) {
1793                 cb_task_data->device_object = FALSE;
1794                 prv_get_all_ms2spec_props(context, cancellable, cb_data);
1795         } else {
1796                 if (root_object)
1797                         msu_props_add_device(
1798                                 (GUPnPDeviceInfo *) context->device_proxy,
1799                                 device,
1800                                 cb_task_data->vb);
1801
1802                 prv_get_all_ms2spec_props(context, cancellable, cb_data);
1803         }
1804
1805         MSU_LOG_DEBUG("Exit");
1806 }
1807
1808 static void prv_get_object_property(GUPnPDIDLLiteParser *parser,
1809                                     GUPnPDIDLLiteObject *object,
1810                                     gpointer user_data)
1811 {
1812         msu_async_cb_data_t *cb_data = user_data;
1813         msu_task_t *task = cb_data->task;
1814         msu_task_get_prop_t *task_data = &task->ut.get_prop;
1815         msu_async_get_prop_t *cb_task_data = &cb_data->ut.get_prop;
1816
1817         if (cb_data->result)
1818                 goto on_error;
1819
1820         cb_data->result = msu_props_get_object_prop(task_data->prop_name,
1821                                                     cb_task_data->root_path,
1822                                                     object);
1823
1824 on_error:
1825
1826         return;
1827 }
1828
1829 static void prv_get_item_property(GUPnPDIDLLiteParser *parser,
1830                                   GUPnPDIDLLiteObject *object,
1831                                   gpointer user_data)
1832 {
1833         msu_async_cb_data_t *cb_data = user_data;
1834         msu_task_t *task = cb_data->task;
1835         msu_task_get_prop_t *task_data = &task->ut.get_prop;
1836         msu_async_get_prop_t *cb_task_data = &cb_data->ut.get_prop;
1837
1838         if (cb_data->result)
1839                 goto on_error;
1840
1841         cb_data->result = msu_props_get_item_prop(task_data->prop_name,
1842                                                   cb_task_data->root_path,
1843                                                   object,
1844                                                   cb_task_data->protocol_info);
1845
1846 on_error:
1847
1848         return;
1849 }
1850
1851 static void prv_get_container_property(GUPnPDIDLLiteParser *parser,
1852                                        GUPnPDIDLLiteObject *object,
1853                                        gpointer user_data)
1854 {
1855         msu_async_cb_data_t *cb_data = user_data;
1856         msu_task_t *task = cb_data->task;
1857         msu_task_get_prop_t *task_data = &task->ut.get_prop;
1858
1859         if (cb_data->result)
1860                 goto on_error;
1861
1862         cb_data->result = msu_props_get_container_prop(task_data->prop_name,
1863                                                        object);
1864
1865 on_error:
1866
1867         return;
1868 }
1869
1870 static void prv_get_all_property(GUPnPDIDLLiteParser *parser,
1871                                  GUPnPDIDLLiteObject *object,
1872                                  gpointer user_data)
1873 {
1874         msu_async_cb_data_t *cb_data = user_data;
1875
1876         prv_get_object_property(parser, object, user_data);
1877
1878         if (cb_data->result)
1879                 goto on_error;
1880
1881         if (GUPNP_IS_DIDL_LITE_CONTAINER(object))
1882                 prv_get_container_property(parser, object, user_data);
1883         else
1884                 prv_get_item_property(parser, object, user_data);
1885
1886 on_error:
1887
1888         return;
1889 }
1890
1891 static gboolean prv_get_child_count_cb(msu_async_cb_data_t *cb_data,
1892                                    gint count)
1893 {
1894         MSU_LOG_DEBUG("Enter");
1895
1896         MSU_LOG_DEBUG("Count %d", count);
1897
1898         cb_data->result =  g_variant_ref_sink(
1899                 g_variant_new_uint32((guint) count));
1900
1901         MSU_LOG_DEBUG("Exit");
1902
1903         return TRUE;
1904 }
1905
1906 static void prv_count_children_cb(GUPnPServiceProxy *proxy,
1907                                   GUPnPServiceProxyAction *action,
1908                                   gpointer user_data)
1909 {
1910         msu_device_count_data_t *count_data = user_data;
1911         msu_async_cb_data_t *cb_data = count_data->cb_data;
1912         GError *upnp_error = NULL;
1913         gint count;
1914         gboolean complete = FALSE;
1915
1916         MSU_LOG_DEBUG("Enter");
1917
1918         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
1919                                             &upnp_error,
1920                                             "TotalMatches", G_TYPE_INT,
1921                                             &count,
1922                                             NULL)) {
1923                 MSU_LOG_WARNING("Browse operation failed: %s",
1924                               upnp_error->message);
1925
1926                 cb_data->error = g_error_new(MSU_ERROR,
1927                                              MSU_ERROR_OPERATION_FAILED,
1928                                              "Browse operation failed: %s",
1929                                              upnp_error->message);
1930                 goto on_error;
1931         }
1932
1933         complete = count_data->cb(cb_data, count);
1934
1935 on_error:
1936
1937         g_free(user_data);
1938
1939         if (cb_data->error || complete) {
1940                 (void) g_idle_add(msu_async_complete_task, cb_data);
1941                 g_cancellable_disconnect(cb_data->cancellable,
1942                                          cb_data->cancel_id);
1943         }
1944
1945         if (upnp_error)
1946                 g_error_free(upnp_error);
1947
1948         MSU_LOG_DEBUG("Exit");
1949 }
1950
1951 static void prv_get_child_count(msu_async_cb_data_t *cb_data,
1952                                 msu_device_count_cb_t cb, const gchar *id)
1953 {
1954         msu_device_count_data_t *count_data;
1955
1956         MSU_LOG_DEBUG("Enter");
1957
1958         prv_msu_device_count_data_new(cb_data, cb, &count_data);
1959         cb_data->action =
1960                 gupnp_service_proxy_begin_action(cb_data->proxy,
1961                                                  "Browse",
1962                                                  prv_count_children_cb,
1963                                                  count_data,
1964                                                  "ObjectID", G_TYPE_STRING, id,
1965
1966                                                  "BrowseFlag", G_TYPE_STRING,
1967                                                  "BrowseDirectChildren",
1968
1969                                                  "Filter", G_TYPE_STRING, "",
1970
1971                                                  "StartingIndex", G_TYPE_INT,
1972                                                  0,
1973
1974                                                  "RequestedCount", G_TYPE_INT,
1975                                                  1,
1976
1977                                                  "SortCriteria", G_TYPE_STRING,
1978                                                  "",
1979
1980                                                  NULL);
1981
1982         MSU_LOG_DEBUG("Exit with SUCCESS");
1983 }
1984
1985 static void prv_get_ms2spec_prop_cb(GUPnPServiceProxy *proxy,
1986                                     GUPnPServiceProxyAction *action,
1987                                     gpointer user_data)
1988 {
1989         GError *upnp_error = NULL;
1990         gchar *result = NULL;
1991         GUPnPDIDLLiteParser *parser = NULL;
1992         msu_async_cb_data_t *cb_data = user_data;
1993         msu_async_get_prop_t *cb_task_data = &cb_data->ut.get_prop;
1994         msu_task_get_prop_t *task_data = &cb_data->task->ut.get_prop;
1995
1996         MSU_LOG_DEBUG("Enter");
1997
1998         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
1999                                             &upnp_error,
2000                                             "Result", G_TYPE_STRING,
2001                                             &result, NULL)) {
2002                 MSU_LOG_WARNING("Browse operation failed: %s",
2003                               upnp_error->message);
2004
2005                 cb_data->error = g_error_new(MSU_ERROR,
2006                                              MSU_ERROR_OPERATION_FAILED,
2007                                              "Browse operation failed: %s",
2008                                              upnp_error->message);
2009                 goto on_error;
2010         }
2011
2012         MSU_LOG_DEBUG("GetMS2SpecProp result: %s", result);
2013
2014         parser = gupnp_didl_lite_parser_new();
2015
2016         g_signal_connect(parser, "object-available" , cb_task_data->prop_func,
2017                          cb_data);
2018
2019         if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error)) {
2020                 if (upnp_error->code == GUPNP_XML_ERROR_EMPTY_NODE) {
2021                         MSU_LOG_WARNING("Property not defined for object");
2022
2023                         cb_data->error =
2024                                 g_error_new(MSU_ERROR,
2025                                             MSU_ERROR_UNKNOWN_PROPERTY,
2026                                             "Property not defined for object");
2027                 } else {
2028                         MSU_LOG_WARNING("Unable to parse results of browse: %s",
2029                                       upnp_error->message);
2030
2031                         cb_data->error =
2032                                 g_error_new(MSU_ERROR,
2033                                             MSU_ERROR_OPERATION_FAILED,
2034                                             "Unable to parse results of "
2035                                             "browse: %s",
2036                                             upnp_error->message);
2037                 }
2038                 goto on_error;
2039         }
2040
2041         if (!cb_data->result) {
2042                 MSU_LOG_WARNING("Property not defined for object");
2043
2044                 cb_data->error = g_error_new(MSU_ERROR,
2045                                              MSU_ERROR_UNKNOWN_PROPERTY,
2046                                              "Property not defined for object");
2047         }
2048
2049 on_error:
2050
2051         if (cb_data->error && !strcmp(task_data->prop_name,
2052                                       MSU_INTERFACE_PROP_CHILD_COUNT)) {
2053                 MSU_LOG_DEBUG("ChildCount not supported by server");
2054
2055                 g_error_free(cb_data->error);
2056                 cb_data->error = NULL;
2057                 prv_get_child_count(cb_data, prv_get_child_count_cb,
2058                                     cb_data->id);
2059         } else {
2060                 (void) g_idle_add(msu_async_complete_task, cb_data);
2061                 g_cancellable_disconnect(cb_data->cancellable,
2062                                          cb_data->cancel_id);
2063         }
2064
2065         if (upnp_error)
2066                 g_error_free(upnp_error);
2067
2068         if (parser)
2069                 g_object_unref(parser);
2070
2071         g_free(result);
2072
2073         MSU_LOG_DEBUG("Exit");
2074 }
2075
2076 static void prv_get_ms2spec_prop(msu_device_context_t *context,
2077                                  msu_prop_map_t *prop_map,
2078                                  msu_task_get_prop_t *task_data,
2079                                  GCancellable *cancellable,
2080                                  msu_async_cb_data_t *cb_data)
2081 {
2082         msu_async_get_prop_t *cb_task_data;
2083         const gchar *filter;
2084
2085         MSU_LOG_DEBUG("Enter");
2086
2087         cb_task_data = &cb_data->ut.get_prop;
2088
2089         if (!prop_map) {
2090                 cb_data->error = g_error_new(MSU_ERROR,
2091                                              MSU_ERROR_UNKNOWN_PROPERTY,
2092                                              "Unknown property");
2093                 goto on_error;
2094         }
2095
2096         filter = prop_map->filter ? prop_map->upnp_prop_name : "";
2097
2098         if (!strcmp(MSU_INTERFACE_MEDIA_CONTAINER, task_data->interface_name)) {
2099                 cb_task_data->prop_func =
2100                         G_CALLBACK(prv_get_container_property);
2101         } else if (!strcmp(MSU_INTERFACE_MEDIA_ITEM,
2102                            task_data->interface_name)) {
2103                 cb_task_data->prop_func = G_CALLBACK(prv_get_item_property);
2104         } else if (!strcmp(MSU_INTERFACE_MEDIA_OBJECT,
2105                            task_data->interface_name)) {
2106                 cb_task_data->prop_func = G_CALLBACK(prv_get_object_property);
2107         } else  if (!strcmp("", task_data->interface_name)) {
2108                 cb_task_data->prop_func = G_CALLBACK(prv_get_all_property);
2109         } else {
2110                 MSU_LOG_WARNING("Interface is unknown.%s",
2111                               task_data->interface_name);
2112
2113                 cb_data->error = g_error_new(MSU_ERROR,
2114                                              MSU_ERROR_UNKNOWN_INTERFACE,
2115                                              "Interface is unknown.");
2116                 goto on_error;
2117         }
2118
2119         cb_data->action = gupnp_service_proxy_begin_action(
2120                 context->service_proxy, "Browse",
2121                 prv_get_ms2spec_prop_cb,
2122                 cb_data,
2123                 "ObjectID", G_TYPE_STRING, cb_data->id,
2124                 "BrowseFlag", G_TYPE_STRING,
2125                 "BrowseMetadata",
2126                 "Filter", G_TYPE_STRING, filter,
2127                 "StartingIndex", G_TYPE_INT, 0,
2128                 "RequestedCount", G_TYPE_INT, 0,
2129                 "SortCriteria", G_TYPE_STRING,
2130                 "",
2131                 NULL);
2132
2133         cb_data->proxy = context->service_proxy;
2134
2135         cb_data->cancel_id =
2136                 g_cancellable_connect(cancellable,
2137                                       G_CALLBACK(msu_async_task_cancelled),
2138                                       cb_data, NULL);
2139         cb_data->cancellable = cancellable;
2140
2141         MSU_LOG_DEBUG("Exit with SUCCESS");
2142
2143         return;
2144
2145 on_error:
2146
2147         (void) g_idle_add(msu_async_complete_task, cb_data);
2148
2149         MSU_LOG_DEBUG("Exit with FAIL");
2150
2151         return;
2152 }
2153
2154 void msu_device_get_prop(msu_device_t *device, msu_client_t *client,
2155                          msu_task_t *task, msu_async_cb_data_t *cb_data,
2156                          msu_prop_map_t *prop_map, gboolean root_object,
2157                          GCancellable *cancellable)
2158 {
2159         msu_task_get_prop_t *task_data = &task->ut.get_prop;
2160         msu_device_context_t *context;
2161         gboolean complete = FALSE;
2162
2163         MSU_LOG_DEBUG("Enter");
2164
2165         context = msu_device_get_context(device, client);
2166
2167         if (!strcmp(task_data->interface_name, MSU_INTERFACE_MEDIA_DEVICE)) {
2168                 if (root_object) {
2169                         if (!strcmp(task_data->prop_name,
2170                                 MSU_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID)) {
2171                                 prv_get_system_update_id_for_prop(
2172                                                         context->service_proxy,
2173                                                         device,
2174                                                         cancellable,
2175                                                         cb_data);
2176                         } else if (!strcmp(task_data->prop_name,
2177                                   MSU_INTERFACE_PROP_ESV_SERVICE_RESET_TOKEN)) {
2178                                 prv_get_sr_token_for_prop(
2179                                                         context->service_proxy,
2180                                                         device,
2181                                                         cancellable,
2182                                                         cb_data);
2183                         } else {
2184                                 cb_data->result =
2185                                         msu_props_get_device_prop(
2186                                                 (GUPnPDeviceInfo *)
2187                                                 context->device_proxy,
2188                                                 device,
2189                                                 task_data->prop_name);
2190
2191                                 if (!cb_data->result)
2192                                         cb_data->error = g_error_new(
2193                                                 MSU_ERROR,
2194                                                 MSU_ERROR_UNKNOWN_PROPERTY,
2195                                                 "Unknown property");
2196
2197                                 (void) g_idle_add(msu_async_complete_task,
2198                                                   cb_data);
2199                         }
2200
2201                 } else {
2202                         cb_data->error =
2203                                 g_error_new(MSU_ERROR,
2204                                             MSU_ERROR_UNKNOWN_INTERFACE,
2205                                             "Interface is unknown.");
2206
2207                         (void) g_idle_add(msu_async_complete_task, cb_data);
2208                 }
2209
2210         } else if (strcmp(task_data->interface_name, "")) {
2211                 prv_get_ms2spec_prop(context, prop_map, &task->ut.get_prop,
2212                                      cancellable, cb_data);
2213         } else {
2214                 if (root_object) {
2215                         if (!strcmp(task_data->prop_name,
2216                                 MSU_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID)) {
2217                                 prv_get_system_update_id_for_prop(
2218                                                         context->service_proxy,
2219                                                         device,
2220                                                         cancellable,
2221                                                         cb_data);
2222                                 complete = TRUE;
2223                         } else if (!strcmp(task_data->prop_name,
2224                                   MSU_INTERFACE_PROP_ESV_SERVICE_RESET_TOKEN)) {
2225                                 prv_get_sr_token_for_prop(
2226                                                         context->service_proxy,
2227                                                         device,
2228                                                         cancellable,
2229                                                         cb_data);
2230                                 complete = TRUE;
2231                         } else {
2232                                 cb_data->result = msu_props_get_device_prop(
2233                                         (GUPnPDeviceInfo *)
2234                                         context->device_proxy,
2235                                         device,
2236                                         task_data->prop_name);
2237                                 if (cb_data->result) {
2238                                         (void) g_idle_add(
2239                                                         msu_async_complete_task,
2240                                                         cb_data);
2241                                         complete = TRUE;
2242                                 }
2243                         }
2244                 }
2245
2246                 if (!complete)
2247                         prv_get_ms2spec_prop(context, prop_map,
2248                                              &task->ut.get_prop, cancellable,
2249                                              cb_data);
2250         }
2251
2252         MSU_LOG_DEBUG("Exit");
2253 }
2254
2255 static void prv_found_target(GUPnPDIDLLiteParser *parser,
2256                              GUPnPDIDLLiteObject *object,
2257                              gpointer user_data)
2258 {
2259         msu_async_cb_data_t *cb_data = user_data;
2260         msu_async_bas_t *cb_task_data = &cb_data->ut.bas;
2261         const char *id;
2262         const char *parent_path;
2263         gchar *path = NULL;
2264         gboolean have_child_count;
2265         msu_device_object_builder_t *builder;
2266
2267         MSU_LOG_DEBUG("Enter");
2268
2269         builder = g_new0(msu_device_object_builder_t, 1);
2270
2271         id = gupnp_didl_lite_object_get_parent_id(object);
2272
2273         if (!id || !strcmp(id, "-1") || !strcmp(id, "")) {
2274                 parent_path = cb_task_data->root_path;
2275         } else {
2276                 path = msu_path_from_id(cb_task_data->root_path, id);
2277                 parent_path = path;
2278         }
2279
2280         builder->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
2281
2282         if (!msu_props_add_object(builder->vb, object, cb_task_data->root_path,
2283                                   parent_path, cb_task_data->filter_mask))
2284                 goto on_error;
2285
2286         if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) {
2287                 msu_props_add_container(builder->vb,
2288                                         (GUPnPDIDLLiteContainer *) object,
2289                                         cb_task_data->filter_mask,
2290                                         &have_child_count);
2291
2292                 if (!have_child_count && (cb_task_data->filter_mask &
2293                                           MSU_UPNP_MASK_PROP_CHILD_COUNT)) {
2294                         builder->needs_child_count = TRUE;
2295                         builder->id = g_strdup(
2296                                 gupnp_didl_lite_object_get_id(object));
2297                         cb_task_data->need_child_count = TRUE;
2298                 }
2299         } else {
2300                 msu_props_add_item(builder->vb,
2301                                    object,
2302                                    cb_task_data->root_path,
2303                                    cb_task_data->filter_mask,
2304                                    cb_task_data->protocol_info);
2305         }
2306
2307         g_ptr_array_add(cb_task_data->vbs, builder);
2308         g_free(path);
2309
2310         MSU_LOG_DEBUG("Exit with SUCCESS");
2311
2312         return;
2313
2314 on_error:
2315
2316         g_free(path);
2317         prv_msu_device_object_builder_delete(builder);
2318
2319         MSU_LOG_DEBUG("Exit with FAIL");
2320 }
2321
2322 static void prv_search_cb(GUPnPServiceProxy *proxy,
2323                           GUPnPServiceProxyAction *action,
2324                           gpointer user_data)
2325 {
2326         gchar *result = NULL;
2327         GUPnPDIDLLiteParser *parser = NULL;
2328         GError *upnp_error = NULL;
2329         msu_async_cb_data_t *cb_data = user_data;
2330         msu_async_bas_t *cb_task_data = &cb_data->ut.bas;
2331
2332         MSU_LOG_DEBUG("Enter");
2333
2334         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
2335                                             &upnp_error,
2336                                             "Result", G_TYPE_STRING,
2337                                             &result,
2338                                             "TotalMatches", G_TYPE_INT,
2339                                             &cb_task_data->max_count,
2340                                             NULL)) {
2341
2342                 MSU_LOG_WARNING("Search operation failed %s",
2343                               upnp_error->message);
2344
2345                 cb_data->error = g_error_new(MSU_ERROR,
2346                                              MSU_ERROR_OPERATION_FAILED,
2347                                              "Search operation failed: %s",
2348                                              upnp_error->message);
2349                 goto on_error;
2350         }
2351
2352         parser = gupnp_didl_lite_parser_new();
2353
2354         cb_task_data->vbs = g_ptr_array_new_with_free_func(
2355                 prv_msu_device_object_builder_delete);
2356
2357         g_signal_connect(parser, "object-available" ,
2358                          G_CALLBACK(prv_found_target), cb_data);
2359
2360         MSU_LOG_DEBUG("Server Search result: %s", result);
2361
2362         if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error)
2363                 && upnp_error->code != GUPNP_XML_ERROR_EMPTY_NODE) {
2364                 MSU_LOG_WARNING("Unable to parse results of search: %s",
2365                               upnp_error->message);
2366
2367                 cb_data->error = g_error_new(MSU_ERROR,
2368                                              MSU_ERROR_OPERATION_FAILED,
2369                                              "Unable to parse results of "
2370                                              "search: %s", upnp_error->message);
2371                 goto on_error;
2372         }
2373
2374         if (cb_task_data->need_child_count) {
2375                 MSU_LOG_DEBUG("Need to retrieve child count");
2376
2377                 if (cb_data->task->multiple_retvals)
2378                         cb_task_data->get_children_cb =
2379                                 prv_get_search_ex_result;
2380                 else
2381                         cb_task_data->get_children_cb = prv_get_children_result;
2382                 prv_retrieve_child_count_for_list(cb_data);
2383                 goto no_complete;
2384         } else {
2385                 if (cb_data->task->multiple_retvals)
2386                         prv_get_search_ex_result(cb_data);
2387                 else
2388                         prv_get_children_result(cb_data);
2389         }
2390
2391 on_error:
2392
2393         (void) g_idle_add(msu_async_complete_task, cb_data);
2394         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
2395
2396 no_complete:
2397
2398         if (parser)
2399                 g_object_unref(parser);
2400
2401         g_free(result);
2402
2403         if (upnp_error)
2404                 g_error_free(upnp_error);
2405
2406         MSU_LOG_DEBUG("Exit");
2407 }
2408
2409 void msu_device_search(msu_device_t *device, msu_client_t *client,
2410                        msu_task_t *task, msu_async_cb_data_t *cb_data,
2411                        const gchar *upnp_filter, const gchar *upnp_query,
2412                        const gchar *sort_by, GCancellable *cancellable)
2413 {
2414         msu_device_context_t *context;
2415
2416         MSU_LOG_DEBUG("Enter");
2417
2418         context = msu_device_get_context(device, client);
2419
2420         cb_data->action = gupnp_service_proxy_begin_action(
2421                 context->service_proxy, "Search",
2422                 prv_search_cb,
2423                 cb_data,
2424                 "ContainerID", G_TYPE_STRING, cb_data->id,
2425                 "SearchCriteria", G_TYPE_STRING, upnp_query,
2426                 "Filter", G_TYPE_STRING, upnp_filter,
2427                 "StartingIndex", G_TYPE_INT, task->ut.search.start,
2428                 "RequestedCount", G_TYPE_INT, task->ut.search.count,
2429                 "SortCriteria", G_TYPE_STRING, sort_by,
2430                 NULL);
2431
2432         cb_data->proxy = context->service_proxy;
2433
2434         cb_data->cancel_id =
2435                 g_cancellable_connect(cancellable,
2436                                       G_CALLBACK(msu_async_task_cancelled),
2437                                       cb_data, NULL);
2438         cb_data->cancellable = cancellable;
2439
2440         MSU_LOG_DEBUG("Exit");
2441 }
2442
2443 static void prv_get_resource(GUPnPDIDLLiteParser *parser,
2444                              GUPnPDIDLLiteObject *object,
2445                              gpointer user_data)
2446 {
2447         msu_async_cb_data_t *cb_data = user_data;
2448         msu_task_t *task = cb_data->task;
2449         msu_task_get_resource_t *task_data = &task->ut.resource;
2450         msu_async_get_all_t *cb_task_data = &cb_data->ut.get_all;
2451
2452         MSU_LOG_DEBUG("Enter");
2453
2454         msu_props_add_resource(cb_task_data->vb, object,
2455                                cb_task_data->filter_mask,
2456                                task_data->protocol_info);
2457 }
2458
2459 void msu_device_get_resource(msu_device_t *device, msu_client_t *client,
2460                              msu_task_t *task, msu_async_cb_data_t *cb_data,
2461                              const gchar *upnp_filter,
2462                              GCancellable *cancellable)
2463 {
2464         msu_async_get_all_t *cb_task_data;
2465         msu_device_context_t *context;
2466
2467         context = msu_device_get_context(device, client);
2468         cb_task_data = &cb_data->ut.get_all;
2469
2470         cb_task_data->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}"));
2471         cb_task_data->prop_func = G_CALLBACK(prv_get_resource);
2472         cb_task_data->device_object = FALSE;
2473
2474         cb_data->action = gupnp_service_proxy_begin_action(
2475                 context->service_proxy, "Browse",
2476                 prv_get_all_ms2spec_props_cb, cb_data,
2477                 "ObjectID", G_TYPE_STRING, cb_data->id,
2478                 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
2479                 "Filter", G_TYPE_STRING, upnp_filter,
2480                 "StartingIndex", G_TYPE_INT, 0,
2481                 "RequestedCount", G_TYPE_INT, 0,
2482                 "SortCriteria", G_TYPE_STRING,
2483                 "", NULL);
2484
2485         cb_data->proxy = context->service_proxy;
2486
2487         cb_data->cancel_id =
2488                 g_cancellable_connect(cancellable,
2489                                       G_CALLBACK(msu_async_task_cancelled),
2490                                       cb_data, NULL);
2491         cb_data->cancellable = cancellable;
2492
2493         MSU_LOG_DEBUG("Exit");
2494 }
2495
2496 static gchar *prv_create_new_container_didl(const gchar *parent_id,
2497                                             msu_task_t *task)
2498 {
2499         GUPnPDIDLLiteWriter *writer;
2500         GUPnPDIDLLiteObject *item;
2501         GUPnPDIDLLiteContainer *container;
2502         gchar *retval;
2503         GVariantIter iter;
2504         GVariant *child_type;
2505         const gchar *actual_type;
2506
2507         writer = gupnp_didl_lite_writer_new(NULL);
2508         item = GUPNP_DIDL_LITE_OBJECT(
2509                                 gupnp_didl_lite_writer_add_container(writer));
2510         container = GUPNP_DIDL_LITE_CONTAINER(item);
2511
2512         gupnp_didl_lite_object_set_id(item, "");
2513         gupnp_didl_lite_object_set_title(item,
2514                                         task->ut.create_container.display_name);
2515         gupnp_didl_lite_object_set_parent_id(item, parent_id);
2516         actual_type = msu_props_media_spec_to_upnp_class(
2517                                                 task->ut.create_container.type);
2518         gupnp_didl_lite_object_set_upnp_class(item, actual_type);
2519         gupnp_didl_lite_object_set_restricted(item, FALSE);
2520         gupnp_didl_lite_object_set_dlna_managed(item, GUPNP_OCM_FLAGS_UPLOAD);
2521
2522         g_variant_iter_init(&iter, task->ut.create_container.child_types);
2523         while ((child_type = g_variant_iter_next_value(&iter))) {
2524                 actual_type = msu_props_media_spec_to_upnp_class(
2525                                         g_variant_get_string(child_type, NULL));
2526                 if (actual_type != NULL)
2527                         gupnp_didl_lite_container_add_create_class(container,
2528                                                                    actual_type);
2529                 g_variant_unref(child_type);
2530         }
2531
2532         retval = gupnp_didl_lite_writer_get_string(writer);
2533
2534         g_object_unref(item);
2535         g_object_unref(writer);
2536
2537         return retval;
2538 }
2539
2540 static gchar *prv_create_upload_didl(const gchar *parent_id, msu_task_t *task,
2541                                      const gchar *object_class,
2542                                      const gchar *mime_type)
2543 {
2544         GUPnPDIDLLiteWriter *writer;
2545         GUPnPDIDLLiteObject *item;
2546         gchar *retval;
2547         GUPnPProtocolInfo *protocol_info;
2548         GUPnPDIDLLiteResource *res;
2549
2550         writer = gupnp_didl_lite_writer_new(NULL);
2551         item = GUPNP_DIDL_LITE_OBJECT(gupnp_didl_lite_writer_add_item(writer));
2552
2553         gupnp_didl_lite_object_set_id(item, "");
2554         gupnp_didl_lite_object_set_title(item, task->ut.upload.display_name);
2555         gupnp_didl_lite_object_set_parent_id(item, parent_id);
2556         gupnp_didl_lite_object_set_upnp_class(item, object_class);
2557         gupnp_didl_lite_object_set_restricted(item, FALSE);
2558
2559         protocol_info = gupnp_protocol_info_new();
2560         gupnp_protocol_info_set_mime_type(protocol_info, mime_type);
2561         gupnp_protocol_info_set_protocol(protocol_info, "*");
2562         gupnp_protocol_info_set_network(protocol_info, "*");
2563
2564         res = gupnp_didl_lite_object_add_resource(item);
2565         gupnp_didl_lite_resource_set_protocol_info(res, protocol_info);
2566
2567         /* TODO: Need to compute DLNA Profile */
2568
2569         retval = gupnp_didl_lite_writer_get_string(writer);
2570
2571         g_object_unref(res);
2572         g_object_unref(protocol_info);
2573         g_object_unref(item);
2574         g_object_unref(writer);
2575
2576         return retval;
2577 }
2578
2579 static void prv_extract_import_uri(GUPnPDIDLLiteParser *parser,
2580                                    GUPnPDIDLLiteObject *object,
2581                                    gpointer user_data)
2582 {
2583         gchar **import_uri = user_data;
2584         GList *resources;
2585         GList *ptr;
2586         GUPnPDIDLLiteResource *res;
2587         const gchar *uri;
2588
2589         if (!*import_uri) {
2590                 resources = gupnp_didl_lite_object_get_resources(object);
2591                 ptr = resources;
2592                 while (ptr) {
2593                         res = ptr->data;
2594                         if (!*import_uri) {
2595                                 uri = gupnp_didl_lite_resource_get_import_uri(
2596                                         res);
2597                                 if (uri)
2598                                         *import_uri = g_strdup(uri);
2599                         }
2600                         g_object_unref(res);
2601                         ptr = ptr->next;
2602                 }
2603
2604                 g_list_free(resources);
2605         }
2606 }
2607
2608 static void prv_upload_delete_cb(GUPnPServiceProxy *proxy,
2609                                  GUPnPServiceProxyAction *action,
2610                                  gpointer user_data)
2611 {
2612         msu_async_cb_data_t *cb_data = user_data;
2613
2614         MSU_LOG_DEBUG("Enter");
2615
2616         (void) gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
2617                                               NULL, NULL);
2618         (void) g_idle_add(msu_async_complete_task, cb_data);
2619         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
2620
2621         MSU_LOG_DEBUG("Exit");
2622 }
2623
2624 static void prv_msu_upload_job_delete(gpointer up_job)
2625 {
2626         msu_device_upload_job_t *upload_job = up_job;
2627
2628         if (up_job) {
2629                 if (upload_job->remove_idle)
2630                         (void) g_source_remove(upload_job->remove_idle);
2631
2632                 g_free(upload_job);
2633         }
2634 }
2635
2636 static gboolean prv_remove_update_job(gpointer user_data)
2637 {
2638         msu_device_upload_job_t *upload_job = user_data;
2639         msu_device_upload_t *upload;
2640
2641         upload = g_hash_table_lookup(upload_job->device->uploads,
2642                                      &upload_job->upload_id);
2643         if (upload) {
2644                 g_hash_table_remove(upload_job->device->uploads,
2645                                     &upload_job->upload_id);
2646
2647                 MSU_LOG_DEBUG("Removing Upload Object: %d",
2648                         upload_job->upload_id);
2649         }
2650
2651         upload_job->remove_idle = 0;
2652         g_hash_table_remove(upload_job->device->upload_jobs,
2653                             &upload_job->upload_id);
2654
2655         return FALSE;
2656 }
2657
2658 static void prv_generate_upload_update(msu_device_upload_job_t *upload_job,
2659                                        msu_device_upload_t *upload)
2660 {
2661         GVariant *args;
2662
2663         args = g_variant_new("(ustt)", upload_job->upload_id, upload->status,
2664                              upload->bytes_uploaded, upload->bytes_to_upload);
2665
2666         MSU_LOG_DEBUG(
2667                 "Emitting: %s (%u %s %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT")"
2668                 " on %s",
2669                 MSU_INTERFACE_UPLOAD_UPDATE, upload_job->upload_id,
2670                 upload->status, upload->bytes_uploaded,
2671                 upload->bytes_to_upload, upload_job->device->path);
2672
2673         (void) g_dbus_connection_emit_signal(upload_job->device->connection,
2674                                              NULL,
2675                                              upload_job->device->path,
2676                                              MSU_INTERFACE_MEDIA_DEVICE,
2677                                              MSU_INTERFACE_UPLOAD_UPDATE,
2678                                              args,
2679                                              NULL);
2680 }
2681
2682 static void prv_post_finished(SoupSession *session, SoupMessage *msg,
2683                               gpointer user_data)
2684 {
2685         msu_device_upload_job_t *upload_job = user_data;
2686         msu_device_upload_t *upload;
2687         gint *upload_id;
2688
2689         MSU_LOG_DEBUG("Enter");
2690
2691         MSU_LOG_DEBUG("Upload %u finished.  Code %u Message %s",
2692                       upload_job->upload_id, msg->status_code,
2693                       msg->reason_phrase);
2694
2695         /* This is clumsy but we need to distinguish between two cases:
2696            1. We cancel because the process is exitting.
2697            2. We cancel because a client has called CancelUpload.
2698
2699            We could use custom SOUP error messages to distinguish the cases
2700            but device->shutting_down seemed less hacky.
2701
2702            We need this check as if we are shutting down it is
2703            dangerous to manipulate uploads as we are being called from its
2704            destructor.
2705         */
2706
2707         if (upload_job->device->shutting_down) {
2708                 MSU_LOG_DEBUG("Device shutting down. Cancelling Upload");
2709                 goto on_error;
2710         }
2711
2712         upload = g_hash_table_lookup(upload_job->device->uploads,
2713                                      &upload_job->upload_id);
2714         if (upload) {
2715                 upload_job->remove_idle =
2716                         g_timeout_add(30000, prv_remove_update_job, user_data);
2717
2718                 if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) {
2719                         upload->status = MSU_UPLOAD_STATUS_COMPLETED;
2720                         upload->bytes_uploaded = upload->bytes_to_upload;
2721                 } else if (msg->status_code == SOUP_STATUS_CANCELLED) {
2722                         upload->status = MSU_UPLOAD_STATUS_CANCELLED;
2723                 } else {
2724                         upload->status = MSU_UPLOAD_STATUS_ERROR;
2725                 }
2726
2727                 MSU_LOG_DEBUG("Upload Status: %s", upload->status);
2728
2729                 prv_generate_upload_update(upload_job, upload);
2730
2731                 g_object_unref(upload->msg);
2732                 upload->msg = NULL;
2733
2734                 g_object_unref(upload->soup_session);
2735                 upload->soup_session = NULL;
2736
2737                 g_mapped_file_unref(upload->mapped_file);
2738                 upload->mapped_file = NULL;
2739
2740                 upload_id = g_new(gint, 1);
2741                 *upload_id = upload_job->upload_id;
2742
2743                 g_hash_table_insert(upload_job->device->upload_jobs, upload_id,
2744                                     upload_job);
2745
2746                 upload_job = NULL;
2747         }
2748
2749 on_error:
2750
2751         prv_msu_upload_job_delete(upload_job);
2752
2753         MSU_LOG_DEBUG("Exit");
2754 }
2755
2756 static void prv_msu_device_upload_delete(gpointer up)
2757 {
2758         msu_device_upload_t *upload = up;
2759
2760         MSU_LOG_DEBUG("Enter");
2761
2762         if (upload) {
2763                 if (upload->msg) {
2764                         soup_session_cancel_message(upload->soup_session,
2765                                                     upload->msg,
2766                                                     SOUP_STATUS_CANCELLED);
2767                         g_object_unref(upload->msg);
2768                 }
2769
2770                 if (upload->soup_session)
2771                         g_object_unref(upload->soup_session);
2772
2773                 if (upload->mapped_file)
2774                         g_mapped_file_unref(upload->mapped_file);
2775
2776                 g_free(upload);
2777         }
2778
2779         MSU_LOG_DEBUG("Exit");
2780 }
2781
2782 static void prv_post_bytes_written(SoupMessage *msg, SoupBuffer *chunk,
2783                                    gpointer user_data)
2784 {
2785         msu_device_upload_t *upload = user_data;
2786
2787         upload->bytes_uploaded += chunk->length;
2788         if (upload->bytes_uploaded > upload->bytes_to_upload)
2789                 upload->bytes_uploaded = upload->bytes_to_upload;
2790 }
2791
2792 static msu_device_upload_t *prv_msu_device_upload_new(const gchar *file_path,
2793                                                       const gchar *import_uri,
2794                                                       const gchar *mime_type,
2795                                                       GError **error)
2796 {
2797         const char *body;
2798         gsize body_length;
2799         msu_device_upload_t *upload;
2800
2801         MSU_LOG_DEBUG("Enter");
2802
2803         upload = g_new0(msu_device_upload_t, 1);
2804
2805         upload->mapped_file = g_mapped_file_new(file_path, FALSE, NULL);
2806         if (!upload->mapped_file) {
2807                 MSU_LOG_WARNING("Unable to map %s into memory", file_path);
2808
2809                 *error = g_error_new(MSU_ERROR, MSU_ERROR_IO,
2810                                      "Unable to map %s into memory",
2811                                      file_path);
2812                 goto on_error;
2813         }
2814
2815         body = g_mapped_file_get_contents(upload->mapped_file);
2816         body_length = g_mapped_file_get_length(upload->mapped_file);
2817
2818         upload->soup_session = soup_session_async_new();
2819         upload->msg = soup_message_new("POST", import_uri);
2820
2821         if (!upload->msg) {
2822                 MSU_LOG_WARNING("Invalid URI %s", import_uri);
2823
2824                 *error = g_error_new(MSU_ERROR, MSU_ERROR_BAD_RESULT,
2825                                      "Invalid URI %s", import_uri);
2826                 goto on_error;
2827         }
2828         upload->status = MSU_UPLOAD_STATUS_IN_PROGRESS;
2829         upload->bytes_to_upload = body_length;
2830
2831         soup_message_headers_set_expectations(upload->msg->request_headers,
2832                                               SOUP_EXPECTATION_CONTINUE);
2833
2834         soup_message_set_request(upload->msg, mime_type, SOUP_MEMORY_STATIC,
2835                                  body, body_length);
2836         g_signal_connect(upload->msg, "wrote-body-data",
2837                          G_CALLBACK(prv_post_bytes_written), upload);
2838
2839         MSU_LOG_DEBUG("Exit with Success");
2840
2841         return upload;
2842
2843 on_error:
2844
2845         prv_msu_device_upload_delete(upload);
2846
2847         MSU_LOG_WARNING("Exit with Fail");
2848
2849         return NULL;
2850 }
2851
2852 static void prv_create_container_cb(GUPnPServiceProxy *proxy,
2853                  GUPnPServiceProxyAction *action,
2854                  gpointer user_data)
2855 {
2856         msu_async_cb_data_t *cb_data = user_data;
2857         msu_async_create_container_t *cb_task_data;
2858         GError *upnp_error = NULL;
2859         gchar *result = NULL;
2860         gchar *object_id = NULL;
2861         gchar *object_path;
2862
2863         MSU_LOG_DEBUG("Enter");
2864
2865         cb_task_data = &cb_data->ut.create_container;
2866
2867         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
2868                                             &upnp_error,
2869                                             "ObjectID", G_TYPE_STRING,
2870                                             &object_id,
2871                                             "Result", G_TYPE_STRING,
2872                                             &result,
2873                                             NULL)) {
2874                 MSU_LOG_ERROR("Create Object operation failed: %s",
2875                                                         upnp_error->message);
2876
2877                 cb_data->error = g_error_new(MSU_ERROR,
2878                                              MSU_ERROR_OPERATION_FAILED,
2879                                              "Create Object operation "
2880                                              " failed: %s",
2881                                              upnp_error->message);
2882                 goto on_error;
2883         }
2884
2885         object_path = msu_path_from_id(cb_task_data->root_path, object_id);
2886         cb_data->result = g_variant_ref_sink(g_variant_new_object_path(
2887                                                                 object_path));
2888         g_free(object_path);
2889
2890 on_error:
2891
2892         (void) g_idle_add(msu_async_complete_task, cb_data);
2893         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
2894
2895         if (object_id)
2896                 g_free(object_id);
2897
2898         if (result)
2899                 g_free(result);
2900
2901         if (upnp_error)
2902                 g_error_free(upnp_error);
2903
2904         MSU_LOG_DEBUG("Exit");
2905 }
2906
2907 static void prv_create_object_cb(GUPnPServiceProxy *proxy,
2908                                  GUPnPServiceProxyAction *action,
2909                                  gpointer user_data)
2910 {
2911         gchar *result = NULL;
2912         GUPnPDIDLLiteParser *parser = NULL;
2913         GError *upnp_error = NULL;
2914         msu_async_cb_data_t *cb_data = user_data;
2915         msu_async_upload_t *cb_task_data = &cb_data->ut.upload;
2916         gchar *import_uri = NULL;
2917         gchar *object_id = NULL;
2918         gboolean delete_needed = FALSE;
2919         GVariant *out_params[2];
2920         gchar *object_path;
2921         msu_device_upload_t *upload;
2922         gint *upload_id;
2923         msu_device_upload_job_t *upload_job;
2924
2925         MSU_LOG_DEBUG("Enter");
2926
2927         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
2928                                             &upnp_error,
2929                                             "ObjectID", G_TYPE_STRING,
2930                                             &object_id,
2931                                             "Result", G_TYPE_STRING,
2932                                             &result,
2933                                             NULL)) {
2934                 MSU_LOG_WARNING("Create Object operation failed: %s",
2935                                 upnp_error->message);
2936
2937                 cb_data->error = g_error_new(MSU_ERROR,
2938                                              MSU_ERROR_OPERATION_FAILED,
2939                                              "Create Object operation "
2940                                              " failed: %s",
2941                                              upnp_error->message);
2942                 goto on_error;
2943         }
2944
2945         delete_needed = TRUE;
2946         parser = gupnp_didl_lite_parser_new();
2947
2948         g_signal_connect(parser, "object-available" ,
2949                          G_CALLBACK(prv_extract_import_uri), &import_uri);
2950
2951         MSU_LOG_DEBUG("Create Object Result: %s", result);
2952
2953         if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error)
2954                 && upnp_error->code != GUPNP_XML_ERROR_EMPTY_NODE) {
2955
2956                 MSU_LOG_WARNING("Unable to parse results of CreateObject: %s",
2957                                 upnp_error->message);
2958
2959                 cb_data->error = g_error_new(MSU_ERROR,
2960                                              MSU_ERROR_OPERATION_FAILED,
2961                                              "Unable to parse results of "
2962                                              "CreateObject: %s",
2963                                              upnp_error->message);
2964                 goto on_error;
2965         }
2966
2967         if (!import_uri) {
2968                 MSU_LOG_WARNING("Missing Import URI");
2969
2970                 cb_data->error = g_error_new(MSU_ERROR,
2971                                              MSU_ERROR_OPERATION_FAILED,
2972                                              "Missing Import URI");
2973                 goto on_error;
2974         }
2975
2976         MSU_LOG_DEBUG("Import URI %s", import_uri);
2977
2978         upload = prv_msu_device_upload_new(cb_data->task->ut.upload.file_path,
2979                                            import_uri, cb_task_data->mime_type,
2980                                            &cb_data->error);
2981         if (!upload)
2982                 goto on_error;
2983
2984         upload_job = g_new0(msu_device_upload_job_t, 1);
2985         upload_job->device = cb_task_data->device;
2986         upload_job->upload_id = (gint) cb_task_data->device->upload_id;
2987
2988         soup_session_queue_message(upload->soup_session, upload->msg,
2989                                    prv_post_finished, upload_job);
2990         g_object_ref(upload->msg);
2991
2992         upload_id = g_new(gint, 1);
2993         *upload_id = upload_job->upload_id;
2994         g_hash_table_insert(cb_task_data->device->uploads, upload_id, upload);
2995
2996         object_path = msu_path_from_id(cb_task_data->root_path, object_id);
2997
2998         MSU_LOG_DEBUG("Upload ID %u", *upload_id);
2999         MSU_LOG_DEBUG("Object ID %s", object_id);
3000         MSU_LOG_DEBUG("Object Path %s", object_path);
3001
3002         out_params[0] = g_variant_new_uint32(*upload_id);
3003         out_params[1] = g_variant_new_object_path(object_path);
3004         cb_data->result = g_variant_ref_sink(g_variant_new_tuple(out_params,
3005                                                                  2));
3006
3007         ++cb_task_data->device->upload_id;
3008         if (cb_task_data->device->upload_id > G_MAXINT)
3009                 cb_task_data->device->upload_id = 0;
3010
3011         g_free(object_path);
3012
3013 on_error:
3014
3015         if (cb_data->error && delete_needed) {
3016                 MSU_LOG_WARNING("Upload failed deleting created object "
3017                                 "with id %s", object_id);
3018
3019                 cb_data->action = gupnp_service_proxy_begin_action(
3020                         cb_data->proxy, "DestroyObject", prv_upload_delete_cb,
3021                         cb_data, "ObjectID", G_TYPE_STRING,
3022                         object_id, NULL);
3023         } else {
3024                 (void) g_idle_add(msu_async_complete_task, cb_data);
3025                 g_cancellable_disconnect(cb_data->cancellable,
3026                                          cb_data->cancel_id);
3027         }
3028
3029         g_free(object_id);
3030         g_free(import_uri);
3031
3032         if (parser)
3033                 g_object_unref(parser);
3034
3035         g_free(result);
3036
3037         if (upnp_error)
3038                 g_error_free(upnp_error);
3039
3040         MSU_LOG_DEBUG("Exit");
3041 }
3042
3043 void msu_device_upload(msu_device_t *device, msu_client_t *client,
3044                        msu_task_t *task, const gchar *parent_id,
3045                        msu_async_cb_data_t *cb_data, GCancellable *cancellable)
3046 {
3047         msu_device_context_t *context;
3048         gchar *didl;
3049         msu_async_upload_t *cb_task_data;
3050
3051         MSU_LOG_DEBUG("Enter");
3052         MSU_LOG_DEBUG("Uploading file to %s", parent_id);
3053
3054         context = msu_device_get_context(device, client);
3055         cb_task_data = &cb_data->ut.upload;
3056
3057         didl = prv_create_upload_didl(parent_id, task,
3058                                       cb_task_data->object_class,
3059                                       cb_task_data->mime_type);
3060
3061         MSU_LOG_DEBUG("DIDL: %s", didl);
3062
3063         cb_data->action = gupnp_service_proxy_begin_action(
3064                 context->service_proxy, "CreateObject",
3065                 prv_create_object_cb, cb_data,
3066                 "ContainerID", G_TYPE_STRING, parent_id,
3067                 "Elements", G_TYPE_STRING, didl,
3068                 NULL);
3069
3070         cb_data->proxy = context->service_proxy;
3071         cb_task_data->device = device;
3072
3073         cb_data->cancel_id =
3074                 g_cancellable_connect(cancellable,
3075                                       G_CALLBACK(msu_async_task_cancelled),
3076                                       cb_data, NULL);
3077         cb_data->cancellable = cancellable;
3078
3079         g_free(didl);
3080
3081         MSU_LOG_DEBUG("Exit");
3082 }
3083
3084 gboolean msu_device_get_upload_status(msu_device_t *device,
3085                                       msu_task_t *task, GError **error)
3086 {
3087         msu_device_upload_t *upload;
3088         gboolean retval = FALSE;
3089         GVariant *out_params[3];
3090         guint upload_id;
3091
3092         MSU_LOG_DEBUG("Enter");
3093
3094         upload_id = task->ut.upload_action.upload_id;
3095
3096         upload = g_hash_table_lookup(device->uploads, &upload_id);
3097         if (!upload) {
3098                 *error = g_error_new(MSU_ERROR, MSU_ERROR_OBJECT_NOT_FOUND,
3099                                      "Unknown Upload ID %u ", upload_id);
3100                 goto on_error;
3101         }
3102
3103         out_params[0] = g_variant_new_string(upload->status);
3104         out_params[1] = g_variant_new_uint64(upload->bytes_uploaded);
3105         out_params[2] = g_variant_new_uint64(upload->bytes_to_upload);
3106
3107         MSU_LOG_DEBUG("Upload ( %s %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT" )",
3108                       upload->status, upload->bytes_uploaded,
3109                       upload->bytes_to_upload);
3110
3111         task->result = g_variant_ref_sink(g_variant_new_tuple(out_params, 3));
3112
3113         retval = TRUE;
3114
3115 on_error:
3116
3117         MSU_LOG_DEBUG("Exit");
3118
3119         return retval;
3120 }
3121
3122 void msu_device_get_upload_ids(msu_device_t *device, msu_task_t *task)
3123 {
3124         GVariantBuilder vb;
3125         GHashTableIter iter;
3126         gpointer key;
3127
3128         MSU_LOG_DEBUG("Enter");
3129
3130         g_variant_builder_init(&vb, G_VARIANT_TYPE("au"));
3131
3132         g_hash_table_iter_init(&iter, device->uploads);
3133         while (g_hash_table_iter_next(&iter, &key, NULL))
3134                 g_variant_builder_add(&vb, "u", (guint32) (*((gint *) key)));
3135
3136         task->result = g_variant_ref_sink(g_variant_builder_end(&vb));
3137
3138         MSU_LOG_DEBUG("Exit");
3139 }
3140
3141 gboolean msu_device_cancel_upload(msu_device_t *device, msu_task_t *task,
3142                                   GError **error)
3143 {
3144         msu_device_upload_t *upload;
3145         gboolean retval = FALSE;
3146         guint upload_id;
3147
3148         MSU_LOG_DEBUG("Enter");
3149
3150         upload_id = task->ut.upload_action.upload_id;
3151
3152         upload = g_hash_table_lookup(device->uploads, &upload_id);
3153         if (!upload) {
3154                 *error = g_error_new(MSU_ERROR, MSU_ERROR_OBJECT_NOT_FOUND,
3155                                      "Unknown Upload ID %u ", upload_id);
3156                 goto on_error;
3157         }
3158
3159         if (upload->msg) {
3160                 soup_session_cancel_message(upload->soup_session, upload->msg,
3161                                             SOUP_STATUS_CANCELLED);
3162                 MSU_LOG_DEBUG("Cancelling Upload %u ", upload_id);
3163         }
3164
3165         retval = TRUE;
3166
3167 on_error:
3168
3169         MSU_LOG_DEBUG("Exit");
3170
3171         return retval;
3172 }
3173
3174 static void prv_destroy_object_cb(GUPnPServiceProxy *proxy,
3175                                   GUPnPServiceProxyAction *action,
3176                                   gpointer user_data)
3177 {
3178         GError *upnp_error = NULL;
3179         msu_async_cb_data_t *cb_data = user_data;
3180
3181         MSU_LOG_DEBUG("Enter");
3182
3183         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
3184                                             &upnp_error,
3185                                             NULL)) {
3186                 MSU_LOG_WARNING("Destroy Object operation failed: %s",
3187                                 upnp_error->message);
3188
3189                 cb_data->error = g_error_new(MSU_ERROR,
3190                                              MSU_ERROR_OPERATION_FAILED,
3191                                              "Destroy Object operation "
3192                                              " failed: %s",
3193                                              upnp_error->message);
3194         }
3195
3196         (void) g_idle_add(msu_async_complete_task, cb_data);
3197         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
3198
3199         if (upnp_error)
3200                 g_error_free(upnp_error);
3201
3202         MSU_LOG_DEBUG("Exit");
3203 }
3204
3205 void msu_device_delete_object(msu_device_t *device, msu_client_t *client,
3206                               msu_task_t *task,
3207                               msu_async_cb_data_t *cb_data,
3208                               GCancellable *cancellable)
3209 {
3210         msu_device_context_t *context;
3211
3212         MSU_LOG_DEBUG("Enter");
3213
3214         context = msu_device_get_context(device, client);
3215
3216         cb_data->action = gupnp_service_proxy_begin_action(
3217                                 context->service_proxy, "DestroyObject",
3218                                 prv_destroy_object_cb, cb_data,
3219                                 "ObjectID", G_TYPE_STRING, cb_data->id,
3220                                 NULL);
3221
3222         cb_data->proxy = context->service_proxy;
3223
3224         cb_data->cancel_id = g_cancellable_connect(cancellable,
3225                                         G_CALLBACK(msu_async_task_cancelled),
3226                                         cb_data, NULL);
3227         cb_data->cancellable = cancellable;
3228
3229         MSU_LOG_DEBUG("Exit");
3230 }
3231
3232 void msu_device_create_container(msu_device_t *device, msu_client_t *client,
3233                                  msu_task_t *task,
3234                                  const gchar *parent_id,
3235                                  msu_async_cb_data_t *cb_data,
3236                                  GCancellable *cancellable)
3237 {
3238         msu_device_context_t *context;
3239         gchar *didl;
3240
3241         MSU_LOG_DEBUG("Enter");
3242
3243         context = msu_device_get_context(device, client);
3244
3245         didl = prv_create_new_container_didl(parent_id, task);
3246
3247         MSU_LOG_DEBUG("DIDL: %s", didl);
3248
3249         cb_data->action = gupnp_service_proxy_begin_action(
3250                                 context->service_proxy, "CreateObject",
3251                                 prv_create_container_cb, cb_data,
3252                                 "ContainerID", G_TYPE_STRING, parent_id,
3253                                 "Elements", G_TYPE_STRING, didl,
3254                                 NULL);
3255
3256         cb_data->proxy = context->service_proxy;
3257
3258         cb_data->cancel_id =
3259                 g_cancellable_connect(cancellable,
3260                                       G_CALLBACK(msu_async_task_cancelled),
3261                                       cb_data, NULL);
3262         cb_data->cancellable = cancellable;
3263
3264         g_free(didl);
3265
3266         MSU_LOG_DEBUG("Exit");
3267 }
3268
3269 static void prv_update_object_update_cb(GUPnPServiceProxy *proxy,
3270                                         GUPnPServiceProxyAction *action,
3271                                         gpointer user_data)
3272 {
3273         GError *upnp_error = NULL;
3274         msu_async_cb_data_t *cb_data = user_data;
3275
3276         MSU_LOG_DEBUG("Enter");
3277
3278         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
3279                                             &upnp_error,
3280                                             NULL)) {
3281                 MSU_LOG_WARNING("Update Object operation failed: %s",
3282                                 upnp_error->message);
3283
3284                 cb_data->error = g_error_new(MSU_ERROR,
3285                                              MSU_ERROR_OPERATION_FAILED,
3286                                              "Update Object operation "
3287                                              " failed: %s",
3288                                              upnp_error->message);
3289         }
3290
3291         (void) g_idle_add(msu_async_complete_task, cb_data);
3292         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
3293
3294         if (upnp_error)
3295                 g_error_free(upnp_error);
3296
3297         MSU_LOG_DEBUG("Exit");
3298 }
3299
3300 static gchar *prv_get_current_xml_fragment(GUPnPDIDLLiteObject *object,
3301                                            guint32 mask)
3302 {
3303         gchar *retval = NULL;
3304
3305         if (mask & MSU_UPNP_MASK_PROP_DISPLAY_NAME)
3306                 retval = gupnp_didl_lite_object_get_title_xml_string(object);
3307         else if (mask & MSU_UPNP_MASK_PROP_ALBUM)
3308                 retval = gupnp_didl_lite_object_get_album_xml_string(object);
3309         else if (mask & MSU_UPNP_MASK_PROP_DATE)
3310                 retval = gupnp_didl_lite_object_get_date_xml_string(object);
3311         else if (mask & MSU_UPNP_MASK_PROP_TYPE)
3312                 retval = gupnp_didl_lite_object_get_upnp_class_xml_string(
3313                         object);
3314         else if (mask & MSU_UPNP_MASK_PROP_TRACK_NUMBER)
3315                 retval = gupnp_didl_lite_object_get_track_number_xml_string(
3316                         object);
3317         else if (mask & MSU_UPNP_MASK_PROP_ARTISTS)
3318                 retval = gupnp_didl_lite_object_get_artists_xml_string(object);
3319
3320         return retval;
3321 }
3322
3323 static gchar *prv_get_new_xml_fragment(GUPnPDIDLLiteObject *object,
3324                                        guint32 mask,
3325                                        GVariant *value)
3326 {
3327         GUPnPDIDLLiteContributor *artist;
3328         const gchar *artist_name;
3329         const gchar *upnp_class;
3330         GVariantIter viter;
3331         gchar *retval = NULL;
3332
3333         if (mask & MSU_UPNP_MASK_PROP_DISPLAY_NAME) {
3334                 gupnp_didl_lite_object_set_title(object,
3335                         g_variant_get_string(value, NULL));
3336
3337                 retval = gupnp_didl_lite_object_get_title_xml_string(object);
3338         } else if (mask & MSU_UPNP_MASK_PROP_ALBUM) {
3339                 gupnp_didl_lite_object_set_album(object,
3340                         g_variant_get_string(value, NULL));
3341
3342                 retval = gupnp_didl_lite_object_get_album_xml_string(object);
3343         } else if (mask & MSU_UPNP_MASK_PROP_DATE) {
3344                 gupnp_didl_lite_object_set_date(object,
3345                         g_variant_get_string(value, NULL));
3346
3347                 retval = gupnp_didl_lite_object_get_date_xml_string(object);
3348         } else if (mask & MSU_UPNP_MASK_PROP_TYPE) {
3349                 upnp_class = msu_props_media_spec_to_upnp_class(
3350                                         g_variant_get_string(value, NULL));
3351
3352                 gupnp_didl_lite_object_set_upnp_class(object, upnp_class);
3353
3354                 retval = gupnp_didl_lite_object_get_upnp_class_xml_string(
3355                         object);
3356         } else if (mask & MSU_UPNP_MASK_PROP_TRACK_NUMBER) {
3357                 gupnp_didl_lite_object_set_track_number(object,
3358                                         g_variant_get_int32(value));
3359
3360                 retval = gupnp_didl_lite_object_get_track_number_xml_string(
3361                         object);
3362         } else if (mask & MSU_UPNP_MASK_PROP_ARTISTS) {
3363                 gupnp_didl_lite_object_unset_artists(object);
3364
3365                 (void) g_variant_iter_init(&viter, value);
3366
3367                 while (g_variant_iter_next(&viter, "&s", &artist_name)) {
3368                         artist = gupnp_didl_lite_object_add_artist(object);
3369
3370                         gupnp_didl_lite_contributor_set_name(artist,
3371                                                              artist_name);
3372                 }
3373
3374                 retval = gupnp_didl_lite_object_get_artists_xml_string(object);
3375         }
3376
3377         return retval;
3378 }
3379
3380 static void prv_get_xml_fragments(GUPnPDIDLLiteParser *parser,
3381                                   GUPnPDIDLLiteObject *object,
3382                                   gpointer user_data)
3383 {
3384         GString *current_str;
3385         GString *new_str;
3386         gchar *frag1;
3387         gchar *frag2;
3388         GVariantIter viter;
3389         const gchar *prop;
3390         GVariant *value;
3391         msu_prop_map_t *prop_map;
3392         GUPnPDIDLLiteWriter *writer;
3393         GUPnPDIDLLiteObject *scratch_object;
3394         gboolean first = TRUE;
3395         msu_async_cb_data_t *cb_data = user_data;
3396         msu_async_update_t *cb_task_data = &cb_data->ut.update;
3397         msu_task_t *task = cb_data->task;
3398         msu_task_update_t *task_data = &task->ut.update;
3399
3400         MSU_LOG_DEBUG("Enter");
3401
3402         current_str = g_string_new("");
3403         new_str = g_string_new("");
3404
3405         writer = gupnp_didl_lite_writer_new(NULL);
3406         if (GUPNP_IS_DIDL_LITE_CONTAINER(object))
3407                 scratch_object = GUPNP_DIDL_LITE_OBJECT(
3408                         gupnp_didl_lite_writer_add_container(writer));
3409         else
3410                 scratch_object = GUPNP_DIDL_LITE_OBJECT(
3411                         gupnp_didl_lite_writer_add_item(writer));
3412
3413         (void) g_variant_iter_init(&viter, task_data->to_add_update);
3414
3415         while (g_variant_iter_next(&viter, "{&sv}", &prop, &value)) {
3416
3417                 MSU_LOG_DEBUG("to_add_update = %s", prop);
3418
3419                 prop_map = g_hash_table_lookup(cb_task_data->map, prop);
3420
3421                 frag1 = prv_get_current_xml_fragment(object, prop_map->type);
3422                 frag2 = prv_get_new_xml_fragment(scratch_object, prop_map->type,
3423                                                  value);
3424
3425                 if (!frag2) {
3426                         MSU_LOG_DEBUG("Unable to set %s.  Skipping", prop);
3427
3428                         g_free(frag1);
3429                         continue;
3430                 }
3431
3432                 if (!first) {
3433                         g_string_append(current_str, ",");
3434                         g_string_append(new_str, ",");
3435                 } else {
3436                         first = FALSE;
3437                 }
3438
3439                 if (frag1) {
3440                         g_string_append(current_str, (const gchar *) frag1);
3441                         g_free(frag1);
3442                 }
3443
3444                 g_string_append(new_str, (const gchar *) frag2);
3445                 g_free(frag2);
3446         }
3447
3448         (void) g_variant_iter_init(&viter, task_data->to_delete);
3449
3450         while (g_variant_iter_next(&viter, "&s", &prop)) {
3451                 MSU_LOG_DEBUG("to_delete = %s", prop);
3452
3453                 prop_map = g_hash_table_lookup(cb_task_data->map, prop);
3454
3455                 frag1 = prv_get_current_xml_fragment(object, prop_map->type);
3456                 if (!frag1)
3457                         continue;
3458
3459                 if (!first)
3460                         g_string_append(current_str, ",");
3461                 else
3462                         first = FALSE;
3463
3464                 g_string_append(current_str, (const gchar *) frag1);
3465                 g_free(frag1);
3466         }
3467
3468         cb_task_data->current_tag_value = g_string_free(current_str, FALSE);
3469         MSU_LOG_DEBUG("current_tag_value = %s",
3470                       cb_task_data->current_tag_value);
3471
3472         cb_task_data->new_tag_value = g_string_free(new_str, FALSE);
3473         MSU_LOG_DEBUG("new_tag_value = %s", cb_task_data->new_tag_value);
3474
3475         g_object_unref(scratch_object);
3476         g_object_unref(writer);
3477
3478         MSU_LOG_DEBUG("Exit");
3479 }
3480
3481 static void prv_update_object_browse_cb(GUPnPServiceProxy *proxy,
3482                                         GUPnPServiceProxyAction *action,
3483                                         gpointer user_data)
3484 {
3485         GError *upnp_error = NULL;
3486         msu_async_cb_data_t *cb_data = user_data;
3487         msu_async_update_t *cb_task_data = &cb_data->ut.update;
3488         GUPnPDIDLLiteParser *parser = NULL;
3489         gchar *result = NULL;
3490
3491         MSU_LOG_DEBUG("Enter");
3492
3493         if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action,
3494                                             &upnp_error,
3495                                             "Result", G_TYPE_STRING,
3496                                             &result, NULL)) {
3497                 MSU_LOG_WARNING("Browse Object operation failed: %s",
3498                                 upnp_error->message);
3499
3500                 cb_data->error = g_error_new(MSU_ERROR,
3501                                              MSU_ERROR_OPERATION_FAILED,
3502                                              "Browse operation failed: %s",
3503                                              upnp_error->message);
3504                 goto on_error;
3505         }
3506
3507         MSU_LOG_DEBUG("msu_device_update_ex_object result: %s", result);
3508
3509         parser = gupnp_didl_lite_parser_new();
3510
3511         g_signal_connect(parser, "object-available",
3512                          G_CALLBACK(prv_get_xml_fragments),
3513                          cb_data);
3514
3515         if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error)) {
3516                 if (upnp_error->code == GUPNP_XML_ERROR_EMPTY_NODE) {
3517                         MSU_LOG_WARNING("Property not defined for object");
3518
3519                         cb_data->error =
3520                                 g_error_new(MSU_ERROR,
3521                                             MSU_ERROR_UNKNOWN_PROPERTY,
3522                                             "Property not defined for object");
3523                 } else {
3524                         MSU_LOG_WARNING("Unable to parse results of browse: %s",
3525                                       upnp_error->message);
3526
3527                         cb_data->error =
3528                                 g_error_new(MSU_ERROR,
3529                                             MSU_ERROR_OPERATION_FAILED,
3530                                             "Unable to parse results of "
3531                                             "browse: %s",
3532                                             upnp_error->message);
3533                 }
3534
3535                 goto on_error;
3536         }
3537
3538         cb_data->action = gupnp_service_proxy_begin_action(
3539                 cb_data->proxy, "UpdateObject",
3540                 prv_update_object_update_cb, cb_data,
3541                 "ObjectID", G_TYPE_STRING, cb_data->id,
3542                 "CurrentTagValue", G_TYPE_STRING,
3543                 cb_task_data->current_tag_value,
3544                 "NewTagValue", G_TYPE_STRING, cb_task_data->new_tag_value,
3545                 NULL);
3546
3547         goto no_complete;
3548
3549 on_error:
3550
3551         (void) g_idle_add(msu_async_complete_task, cb_data);
3552         g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id);
3553
3554 no_complete:
3555
3556         if (parser)
3557                 g_object_unref(parser);
3558
3559         g_free(result);
3560
3561         if (upnp_error)
3562                 g_error_free(upnp_error);
3563
3564         MSU_LOG_DEBUG("Exit");
3565 }
3566
3567 void msu_device_update_object(msu_device_t *device, msu_client_t *client,
3568                               msu_task_t *task,
3569                               msu_async_cb_data_t *cb_data,
3570                               const gchar *upnp_filter,
3571                               GCancellable *cancellable)
3572 {
3573         msu_device_context_t *context;
3574
3575         MSU_LOG_DEBUG("Enter");
3576
3577         context = msu_device_get_context(device, client);
3578
3579         cb_data->action = gupnp_service_proxy_begin_action(
3580                                 context->service_proxy, "Browse",
3581                                 prv_update_object_browse_cb, cb_data,
3582                                 "ObjectID", G_TYPE_STRING, cb_data->id,
3583                                 "BrowseFlag", G_TYPE_STRING, "BrowseMetadata",
3584                                 "Filter", G_TYPE_STRING, upnp_filter,
3585                                 "StartingIndex", G_TYPE_INT, 0,
3586                                 "RequestedCount", G_TYPE_INT, 0,
3587                                 "SortCriteria", G_TYPE_STRING,
3588                                 "", NULL);
3589
3590         cb_data->proxy = context->service_proxy;
3591
3592         cb_data->cancel_id = g_cancellable_connect(cancellable,
3593                                         G_CALLBACK(msu_async_task_cancelled),
3594                                         cb_data, NULL);
3595
3596         cb_data->cancellable = cancellable;
3597
3598         MSU_LOG_DEBUG("Exit");
3599 }