tizen 2.3 release
[framework/connectivity/bluez.git] / profiles / audio / player.c
1 /*
2  *
3  *  BlueZ - Bluetooth protocol stack for Linux
4  *
5  *  Copyright (C) 2006-2007  Nokia Corporation
6  *  Copyright (C) 2004-2009  Marcel Holtmann <marcel@holtmann.org>
7  *  Copyright (C) 2012-2012  Intel Corporation
8  *
9  *  This program is free software; you can redistribute it and/or modify
10  *  it under the terms of the GNU General Public License as published by
11  *  the Free Software Foundation; either version 2 of the License, or
12  *  (at your option) any later version.
13  *
14  *  This program is distributed in the hope that it will be useful,
15  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *  GNU General Public License for more details.
18  *
19  *  You should have received a copy of the GNU General Public License
20  *  along with this program; if not, write to the Free Software
21  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22  *
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #include <config.h>
27 #endif
28
29 #include <stdlib.h>
30 #include <stdint.h>
31 #include <inttypes.h>
32 #include <stdbool.h>
33 #include <errno.h>
34 #include <unistd.h>
35 #include <string.h>
36
37 #include <glib.h>
38 #include <dbus/dbus.h>
39 #include <gdbus/gdbus.h>
40
41 #include "src/log.h"
42 #include "src/dbus-common.h"
43 #include "src/error.h"
44
45 #include "player.h"
46
47 #define MEDIA_PLAYER_INTERFACE "org.bluez.MediaPlayer1"
48 #define MEDIA_FOLDER_INTERFACE "org.bluez.MediaFolder1"
49 #define MEDIA_ITEM_INTERFACE "org.bluez.MediaItem1"
50
51 struct player_callback {
52         const struct media_player_callback *cbs;
53         void *user_data;
54 };
55
56 struct pending_req {
57         GDBusPendingPropertySet id;
58         const char *key;
59         const char *value;
60 };
61
62 struct media_item {
63         struct media_player     *player;
64         char                    *path;          /* Item object path */
65         char                    *name;          /* Item name */
66         player_item_type_t      type;           /* Item type */
67         player_folder_type_t    folder_type;    /* Folder type */
68         bool                    playable;       /* Item playable flag */
69         uint64_t                uid;            /* Item uid */
70         GHashTable              *metadata;      /* Item metadata */
71 };
72
73 struct media_folder {
74         struct media_folder     *parent;
75         struct media_item       *item;          /* Folder item */
76         uint32_t                number_of_items;/* Number of items */
77         GSList                  *subfolders;
78         GSList                  *items;
79         DBusMessage             *msg;
80 };
81
82 struct media_player {
83         char                    *device;        /* Device path */
84         char                    *name;          /* Player name */
85         char                    *type;          /* Player type */
86         char                    *subtype;       /* Player subtype */
87         bool                    browsable;      /* Player browsing feature */
88         bool                    searchable;     /* Player searching feature */
89         struct media_folder     *scope;         /* Player current scope */
90         struct media_folder     *folder;        /* Player current folder */
91         struct media_folder     *search;        /* Player search folder */
92         struct media_folder     *playlist;      /* Player current playlist */
93         char                    *path;          /* Player object path */
94         GHashTable              *settings;      /* Player settings */
95         GHashTable              *track;         /* Player current track */
96         char                    *status;
97         uint32_t                position;
98         GTimer                  *progress;
99         guint                   process_id;
100         struct player_callback  *cb;
101         GSList                  *pending;
102         GSList                  *folders;
103 };
104
105 static void append_track(void *key, void *value, void *user_data)
106 {
107         DBusMessageIter *dict = user_data;
108         const char *strkey = key;
109
110         if (strcasecmp(strkey, "Duration") == 0 ||
111                         strcasecmp(strkey, "TrackNumber") == 0 ||
112                         strcasecmp(strkey, "NumberOfTracks") == 0)  {
113                 uint32_t num = atoi(value);
114                 dict_append_entry(dict, key, DBUS_TYPE_UINT32, &num);
115         } else if (strcasecmp(strkey, "Item") == 0) {
116                 dict_append_entry(dict, key, DBUS_TYPE_OBJECT_PATH, &value);
117         } else {
118                 dict_append_entry(dict, key, DBUS_TYPE_STRING, &value);
119         }
120 }
121
122 static struct pending_req *find_pending(struct media_player *mp,
123                                                         const char *key)
124 {
125         GSList *l;
126
127         for (l = mp->pending; l; l = l->next) {
128                 struct pending_req *p = l->data;
129
130                 if (strcasecmp(key, p->key) == 0)
131                         return p;
132         }
133
134         return NULL;
135 }
136
137 static struct pending_req *pending_new(GDBusPendingPropertySet id,
138                                         const char *key, const char *value)
139 {
140         struct pending_req *p;
141
142         p = g_new0(struct pending_req, 1);
143         p->id = id;
144         p->key = key;
145         p->value = value;
146
147         return p;
148 }
149
150 static uint32_t media_player_get_position(struct media_player *mp)
151 {
152         double timedelta;
153         uint32_t sec, msec;
154
155         if (g_strcmp0(mp->status, "playing") != 0 ||
156                                                 mp->position == UINT32_MAX)
157                 return mp->position;
158
159         timedelta = g_timer_elapsed(mp->progress, NULL);
160
161         sec = (uint32_t) timedelta;
162         msec = (uint32_t) ((timedelta - sec) * 1000);
163
164         return mp->position + sec * 1000 + msec;
165 }
166
167 static gboolean get_position(const GDBusPropertyTable *property,
168                                         DBusMessageIter *iter, void *data)
169 {
170         struct media_player *mp = data;
171         uint32_t position;
172
173         position = media_player_get_position(mp);
174
175         dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32, &position);
176
177         return TRUE;
178 }
179
180 static gboolean status_exists(const GDBusPropertyTable *property, void *data)
181 {
182         struct media_player *mp = data;
183
184         return mp->status != NULL;
185 }
186
187 static gboolean get_status(const GDBusPropertyTable *property,
188                                         DBusMessageIter *iter, void *data)
189 {
190         struct media_player *mp = data;
191
192         if (mp->status == NULL)
193                 return FALSE;
194
195         DBG("%s", mp->status);
196
197         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->status);
198
199         return TRUE;
200 }
201
202 static gboolean setting_exists(const GDBusPropertyTable *property, void *data)
203 {
204         struct media_player *mp = data;
205         const char *value;
206
207         value = g_hash_table_lookup(mp->settings, property->name);
208
209         return value ? TRUE : FALSE;
210 }
211
212 static gboolean get_setting(const GDBusPropertyTable *property,
213                                         DBusMessageIter *iter, void *data)
214 {
215         struct media_player *mp = data;
216         const char *value;
217
218         value = g_hash_table_lookup(mp->settings, property->name);
219         if (value == NULL)
220                 return FALSE;
221
222         DBG("%s %s", property->name, value);
223
224         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &value);
225
226         return TRUE;
227 }
228
229 static void player_set_setting(struct media_player *mp,
230                                         GDBusPendingPropertySet id,
231                                         const char *key, const char *value)
232 {
233         struct player_callback *cb = mp->cb;
234         struct pending_req *p;
235
236         if (cb == NULL || cb->cbs->set_setting == NULL) {
237                 g_dbus_pending_property_error(id,
238                                         ERROR_INTERFACE ".NotSupported",
239                                         "Operation is not supported");
240                 return;
241         }
242
243         p = find_pending(mp, key);
244         if (p != NULL) {
245                 g_dbus_pending_property_error(id,
246                                         ERROR_INTERFACE ".InProgress",
247                                         "Operation already in progress");
248                 return;
249         }
250
251         if (!cb->cbs->set_setting(mp, key, value, cb->user_data)) {
252                 g_dbus_pending_property_error(id,
253                                         ERROR_INTERFACE ".InvalidArguments",
254                                         "Invalid arguments in method call");
255                 return;
256         }
257
258         p = pending_new(id, key, value);
259
260         mp->pending = g_slist_append(mp->pending, p);
261 }
262
263 static void set_setting(const GDBusPropertyTable *property,
264                         DBusMessageIter *iter, GDBusPendingPropertySet id,
265                         void *data)
266 {
267         struct media_player *mp = data;
268         const char *value, *current;
269
270         if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) {
271                 g_dbus_pending_property_error(id,
272                                         ERROR_INTERFACE ".InvalidArguments",
273                                         "Invalid arguments in method call");
274                 return;
275         }
276
277         dbus_message_iter_get_basic(iter, &value);
278
279         current = g_hash_table_lookup(mp->settings, property->name);
280         if (g_strcmp0(current, value) == 0) {
281                 g_dbus_pending_property_success(id);
282                 return;
283         }
284
285         player_set_setting(mp, id, property->name, value);
286 }
287
288 static gboolean track_exists(const GDBusPropertyTable *property, void *data)
289 {
290         struct media_player *mp = data;
291
292         return g_hash_table_size(mp->track) != 0;
293 }
294
295 static gboolean get_track(const GDBusPropertyTable *property,
296                                         DBusMessageIter *iter, void *data)
297 {
298         struct media_player *mp = data;
299         DBusMessageIter dict;
300
301         dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
302                                         DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
303                                         DBUS_TYPE_STRING_AS_STRING
304                                         DBUS_TYPE_VARIANT_AS_STRING
305                                         DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
306                                         &dict);
307
308         g_hash_table_foreach(mp->track, append_track, &dict);
309
310         dbus_message_iter_close_container(iter, &dict);
311
312         return TRUE;
313 }
314
315 static gboolean get_device(const GDBusPropertyTable *property,
316                                         DBusMessageIter *iter, void *data)
317 {
318         struct media_player *mp = data;
319
320         dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
321                                                                 &mp->device);
322
323         return TRUE;
324 }
325
326 static gboolean name_exists(const GDBusPropertyTable *property, void *data)
327 {
328         struct media_player *mp = data;
329
330         return mp->name != NULL;
331 }
332
333 static gboolean get_name(const GDBusPropertyTable *property,
334                                         DBusMessageIter *iter, void *data)
335 {
336         struct media_player *mp = data;
337
338         if (mp->name == NULL)
339                 return FALSE;
340
341         DBG("%s", mp->name);
342
343         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->name);
344
345         return TRUE;
346 }
347
348 static gboolean type_exists(const GDBusPropertyTable *property, void *data)
349 {
350         struct media_player *mp = data;
351
352         return mp->type != NULL;
353 }
354
355 static gboolean get_type(const GDBusPropertyTable *property,
356                                         DBusMessageIter *iter, void *data)
357 {
358         struct media_player *mp = data;
359
360         if (mp->type == NULL)
361                 return FALSE;
362
363         DBG("%s", mp->type);
364
365         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->type);
366
367         return TRUE;
368 }
369
370 static gboolean subtype_exists(const GDBusPropertyTable *property, void *data)
371 {
372         struct media_player *mp = data;
373
374         return mp->subtype != NULL;
375 }
376
377 static gboolean get_subtype(const GDBusPropertyTable *property,
378                                         DBusMessageIter *iter, void *data)
379 {
380         struct media_player *mp = data;
381
382         if (mp->subtype == NULL)
383                 return FALSE;
384
385         DBG("%s", mp->subtype);
386
387         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &mp->subtype);
388
389         return TRUE;
390 }
391
392 static gboolean browsable_exists(const GDBusPropertyTable *property, void *data)
393 {
394         struct media_player *mp = data;
395
396         return mp->scope != NULL;
397 }
398
399 static gboolean get_browsable(const GDBusPropertyTable *property,
400                                         DBusMessageIter *iter, void *data)
401 {
402         struct media_player *mp = data;
403         dbus_bool_t value;
404
405         if (mp->scope == NULL)
406                 return FALSE;
407
408         DBG("%s", mp->browsable ? "true" : "false");
409
410         value = mp->browsable;
411
412         dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
413
414         return TRUE;
415 }
416
417 static gboolean searchable_exists(const GDBusPropertyTable *property,
418                                                                 void *data)
419 {
420         struct media_player *mp = data;
421
422         return mp->scope != NULL;
423 }
424
425 static gboolean get_searchable(const GDBusPropertyTable *property,
426                                         DBusMessageIter *iter, void *data)
427 {
428         struct media_player *mp = data;
429         dbus_bool_t value;
430
431         if (mp->folder == NULL)
432                 return FALSE;
433
434         DBG("%s", mp->searchable ? "true" : "false");
435
436         value = mp->searchable;
437
438         dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
439
440         return TRUE;
441 }
442
443 static gboolean playlist_exists(const GDBusPropertyTable *property,
444                                                                 void *data)
445 {
446         struct media_player *mp = data;
447
448         return mp->playlist != NULL;
449 }
450
451 static gboolean get_playlist(const GDBusPropertyTable *property,
452                                         DBusMessageIter *iter, void *data)
453 {
454         struct media_player *mp = data;
455         struct media_folder *playlist = mp->playlist;
456
457         if (playlist == NULL)
458                 return FALSE;
459
460         dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
461                                                         &playlist->item->path);
462
463         return TRUE;
464 }
465
466 static DBusMessage *media_player_play(DBusConnection *conn, DBusMessage *msg,
467                                                                 void *data)
468 {
469         struct media_player *mp = data;
470         struct player_callback *cb = mp->cb;
471         int err;
472
473         if (cb->cbs->play == NULL)
474                 return btd_error_not_supported(msg);
475
476         err = cb->cbs->play(mp, cb->user_data);
477         if (err < 0)
478                 return btd_error_failed(msg, strerror(-err));
479
480         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
481 }
482
483 static DBusMessage *media_player_pause(DBusConnection *conn, DBusMessage *msg,
484                                                                 void *data)
485 {
486         struct media_player *mp = data;
487         struct player_callback *cb = mp->cb;
488         int err;
489
490         if (cb->cbs->pause == NULL)
491                 return btd_error_not_supported(msg);
492
493         err = cb->cbs->pause(mp, cb->user_data);
494         if (err < 0)
495                 return btd_error_failed(msg, strerror(-err));
496
497         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
498 }
499
500 static DBusMessage *media_player_stop(DBusConnection *conn, DBusMessage *msg,
501                                                                 void *data)
502 {
503         struct media_player *mp = data;
504         struct player_callback *cb = mp->cb;
505         int err;
506
507         if (cb->cbs->stop == NULL)
508                 return btd_error_not_supported(msg);
509
510         err = cb->cbs->stop(mp, cb->user_data);
511         if (err < 0)
512                 return btd_error_failed(msg, strerror(-err));
513
514         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
515 }
516
517 static DBusMessage *media_player_next(DBusConnection *conn, DBusMessage *msg,
518                                                                 void *data)
519 {
520         struct media_player *mp = data;
521         struct player_callback *cb = mp->cb;
522         int err;
523
524         if (cb->cbs->next == NULL)
525                 return btd_error_not_supported(msg);
526
527         err = cb->cbs->next(mp, cb->user_data);
528         if (err < 0)
529                 return btd_error_failed(msg, strerror(-err));
530
531         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
532 }
533
534 static DBusMessage *media_player_previous(DBusConnection *conn,
535                                                 DBusMessage *msg, void *data)
536 {
537         struct media_player *mp = data;
538         struct player_callback *cb = mp->cb;
539         int err;
540
541         if (cb->cbs->previous == NULL)
542                 return btd_error_not_supported(msg);
543
544         err = cb->cbs->previous(mp, cb->user_data);
545         if (err < 0)
546                 return btd_error_failed(msg, strerror(-err));
547
548         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
549 }
550
551 static DBusMessage *media_player_fast_forward(DBusConnection *conn,
552                                                 DBusMessage *msg, void *data)
553 {
554         struct media_player *mp = data;
555         struct player_callback *cb = mp->cb;
556         int err;
557
558         if (cb->cbs->fast_forward == NULL)
559                 return btd_error_not_supported(msg);
560
561         err = cb->cbs->fast_forward(mp, cb->user_data);
562         if (err < 0)
563                 return btd_error_failed(msg, strerror(-err));
564
565         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
566 }
567
568 static DBusMessage *media_player_rewind(DBusConnection *conn, DBusMessage *msg,
569                                                                 void *data)
570 {
571         struct media_player *mp = data;
572         struct player_callback *cb = mp->cb;
573         int err;
574
575         if (cb->cbs->rewind == NULL)
576                 return btd_error_not_supported(msg);
577
578         err = cb->cbs->rewind(mp, cb->user_data);
579         if (err < 0)
580                 return btd_error_failed(msg, strerror(-err));
581
582         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
583 }
584
585 static void parse_folder_list(gpointer data, gpointer user_data)
586 {
587         struct media_item *item = data;
588         DBusMessageIter *array = user_data;
589         DBusMessageIter entry;
590
591         dbus_message_iter_open_container(array, DBUS_TYPE_DICT_ENTRY, NULL,
592                                                                 &entry);
593
594         dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH,
595                                                                 &item->path);
596
597         g_dbus_get_properties(btd_get_dbus_connection(), item->path,
598                                                 MEDIA_ITEM_INTERFACE, &entry);
599
600         dbus_message_iter_close_container(array, &entry);
601 }
602
603 void media_player_change_folder_complete(struct media_player *mp,
604                                                 const char *path, int ret)
605 {
606         struct media_folder *folder = mp->scope;
607         DBusMessage *reply;
608
609         if (folder == NULL || folder->msg == NULL)
610                 return;
611
612         if (ret < 0) {
613                 reply = btd_error_failed(folder->msg, strerror(-ret));
614                 goto done;
615         }
616
617         media_player_set_folder(mp, path, ret);
618
619         reply = g_dbus_create_reply(folder->msg, DBUS_TYPE_INVALID);
620
621 done:
622         g_dbus_send_message(btd_get_dbus_connection(), reply);
623         dbus_message_unref(folder->msg);
624         folder->msg = NULL;
625 }
626
627 void media_player_list_complete(struct media_player *mp, GSList *items,
628                                                                 int err)
629 {
630         struct media_folder *folder = mp->scope;
631         DBusMessage *reply;
632         DBusMessageIter iter, array;
633
634         if (folder == NULL || folder->msg == NULL)
635                 return;
636
637         if (err < 0) {
638                 reply = btd_error_failed(folder->msg, strerror(-err));
639                 goto done;
640         }
641
642         reply = dbus_message_new_method_return(folder->msg);
643
644         dbus_message_iter_init_append(reply, &iter);
645
646         dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
647                                         DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
648                                         DBUS_TYPE_OBJECT_PATH_AS_STRING
649                                         DBUS_TYPE_ARRAY_AS_STRING
650                                         DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
651                                         DBUS_TYPE_STRING_AS_STRING
652                                         DBUS_TYPE_VARIANT_AS_STRING
653                                         DBUS_DICT_ENTRY_END_CHAR_AS_STRING
654                                         DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
655                                         &array);
656
657         g_slist_foreach(items, parse_folder_list, &array);
658         dbus_message_iter_close_container(&iter, &array);
659
660 done:
661         g_dbus_send_message(btd_get_dbus_connection(), reply);
662         dbus_message_unref(folder->msg);
663         folder->msg = NULL;
664 }
665
666 static struct media_item *
667 media_player_create_subfolder(struct media_player *mp, const char *name,
668                                                                 uint64_t uid)
669 {
670         struct media_folder *folder = mp->scope;
671         struct media_item *item;
672         char *path;
673
674         path = g_strdup_printf("%s/%s", folder->item->name, name);
675
676         DBG("%s", path);
677
678         item = media_player_create_item(mp, path, PLAYER_ITEM_TYPE_FOLDER,
679                                                                         uid);
680         g_free(path);
681
682         return item;
683 }
684
685 void media_player_search_complete(struct media_player *mp, int ret)
686 {
687         struct media_folder *folder = mp->scope;
688         struct media_folder *search = mp->search;
689         DBusMessage *reply;
690
691         if (folder == NULL || folder->msg == NULL)
692                 return;
693
694         if (ret < 0) {
695                 reply = btd_error_failed(folder->msg, strerror(-ret));
696                 goto done;
697         }
698
699         if (search == NULL) {
700                 search = g_new0(struct media_folder, 1);
701                 search->item = media_player_create_subfolder(mp, "search", 0);
702                 mp->search = search;
703                 mp->folders = g_slist_prepend(mp->folders, search);
704         }
705
706         search->number_of_items = ret;
707
708         reply = g_dbus_create_reply(folder->msg,
709                                 DBUS_TYPE_OBJECT_PATH, &search->item->path,
710                                 DBUS_TYPE_INVALID);
711
712 done:
713         g_dbus_send_message(btd_get_dbus_connection(), reply);
714         dbus_message_unref(folder->msg);
715         folder->msg = NULL;
716 }
717
718 static const GDBusMethodTable media_player_methods[] = {
719         { GDBUS_EXPERIMENTAL_METHOD("Play", NULL, NULL, media_player_play) },
720         { GDBUS_EXPERIMENTAL_METHOD("Pause", NULL, NULL, media_player_pause) },
721         { GDBUS_EXPERIMENTAL_METHOD("Stop", NULL, NULL, media_player_stop) },
722         { GDBUS_EXPERIMENTAL_METHOD("Next", NULL, NULL, media_player_next) },
723         { GDBUS_EXPERIMENTAL_METHOD("Previous", NULL, NULL,
724                                                 media_player_previous) },
725         { GDBUS_EXPERIMENTAL_METHOD("FastForward", NULL, NULL,
726                                                 media_player_fast_forward) },
727         { GDBUS_EXPERIMENTAL_METHOD("Rewind", NULL, NULL,
728                                                 media_player_rewind) },
729         { }
730 };
731
732 static const GDBusSignalTable media_player_signals[] = {
733         { }
734 };
735
736 static const GDBusPropertyTable media_player_properties[] = {
737         { "Name", "s", get_name, NULL, name_exists,
738                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
739         { "Type", "s", get_type, NULL, type_exists,
740                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
741         { "Subtype", "s", get_subtype, NULL, subtype_exists,
742                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
743         { "Position", "u", get_position, NULL, NULL,
744                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
745         { "Status", "s", get_status, NULL, status_exists,
746                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
747         { "Equalizer", "s", get_setting, set_setting, setting_exists,
748                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
749         { "Repeat", "s", get_setting, set_setting, setting_exists,
750                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
751         { "Shuffle", "s", get_setting, set_setting, setting_exists,
752                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
753         { "Scan", "s", get_setting, set_setting, setting_exists,
754                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
755         { "Track", "a{sv}", get_track, NULL, track_exists,
756                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
757         { "Device", "o", get_device, NULL, NULL,
758                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
759         { "Browsable", "b", get_browsable, NULL, browsable_exists,
760                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
761         { "Searchable", "b", get_searchable, NULL, searchable_exists,
762                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
763         { "Playlist", "o", get_playlist, NULL, playlist_exists,
764                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
765         { }
766 };
767
768 static DBusMessage *media_folder_search(DBusConnection *conn, DBusMessage *msg,
769                                                                 void *data)
770 {
771         struct media_player *mp = data;
772         struct media_folder *folder = mp->scope;
773         struct player_callback *cb = mp->cb;
774         DBusMessageIter iter;
775         const char *string;
776         int err;
777
778         dbus_message_iter_init(msg, &iter);
779
780         if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING)
781                 return btd_error_failed(msg, strerror(EINVAL));
782
783         dbus_message_iter_get_basic(&iter, &string);
784
785         if (!mp->searchable || folder != mp->folder)
786                 return btd_error_failed(msg, strerror(ENOTSUP));
787
788         if (folder->msg != NULL)
789                 return btd_error_failed(msg, strerror(EINVAL));
790
791         if (cb->cbs->search == NULL)
792                 return btd_error_failed(msg, strerror(ENOTSUP));
793
794         err = cb->cbs->search(mp, string, cb->user_data);
795         if (err < 0)
796                 return btd_error_failed(msg, strerror(-err));
797
798         folder->msg = dbus_message_ref(msg);
799
800         return NULL;
801 }
802
803 static int parse_filters(struct media_player *player, DBusMessageIter *iter,
804                                                 uint32_t *start, uint32_t *end)
805 {
806         struct media_folder *folder = player->scope;
807         DBusMessageIter dict;
808         int ctype;
809
810         *start = 0;
811         *end = folder->number_of_items ? folder->number_of_items - 1 :
812                                                                 UINT32_MAX;
813
814         ctype = dbus_message_iter_get_arg_type(iter);
815         if (ctype != DBUS_TYPE_ARRAY)
816                 return FALSE;
817
818         dbus_message_iter_recurse(iter, &dict);
819
820         while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
821                                                         DBUS_TYPE_INVALID) {
822                 DBusMessageIter entry, var;
823                 const char *key;
824
825                 if (ctype != DBUS_TYPE_DICT_ENTRY)
826                         return -EINVAL;
827
828                 dbus_message_iter_recurse(&dict, &entry);
829                 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING)
830                         return -EINVAL;
831
832                 dbus_message_iter_get_basic(&entry, &key);
833                 dbus_message_iter_next(&entry);
834
835                 if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT)
836                         return -EINVAL;
837
838                 dbus_message_iter_recurse(&entry, &var);
839
840                 if (dbus_message_iter_get_arg_type(&var) != DBUS_TYPE_UINT32)
841                         return -EINVAL;
842
843                 if (strcasecmp(key, "Start") == 0)
844                         dbus_message_iter_get_basic(&var, start);
845                 else if (strcasecmp(key, "End") == 0)
846                         dbus_message_iter_get_basic(&var, end);
847
848                 dbus_message_iter_next(&dict);
849         }
850
851         if (folder->number_of_items > 0 && *end > folder->number_of_items)
852                 *end = folder->number_of_items;
853
854         return 0;
855 }
856
857 static DBusMessage *media_folder_list_items(DBusConnection *conn,
858                                                 DBusMessage *msg, void *data)
859 {
860         struct media_player *mp = data;
861         struct media_folder *folder = mp->scope;
862         struct player_callback *cb = mp->cb;
863         DBusMessageIter iter;
864         uint32_t start, end;
865         int err;
866
867         dbus_message_iter_init(msg, &iter);
868
869         if (parse_filters(mp, &iter, &start, &end) < 0)
870                 return btd_error_invalid_args(msg);
871
872         if (cb->cbs->list_items == NULL)
873                 return btd_error_not_supported(msg);
874
875         if (folder->msg != NULL)
876                 return btd_error_failed(msg, strerror(EBUSY));
877
878         err = cb->cbs->list_items(mp, folder->item->name, start, end,
879                                                         cb->user_data);
880         if (err < 0)
881                 return btd_error_failed(msg, strerror(-err));
882
883         folder->msg = dbus_message_ref(msg);
884
885         return NULL;
886 }
887
888 static void media_item_free(struct media_item *item)
889 {
890         if (item->metadata != NULL)
891                 g_hash_table_unref(item->metadata);
892
893         g_free(item->path);
894         g_free(item->name);
895         g_free(item);
896 }
897
898 static void media_item_destroy(void *data)
899 {
900         struct media_item *item = data;
901
902         DBG("%s", item->path);
903
904         g_dbus_unregister_interface(btd_get_dbus_connection(), item->path,
905                                                 MEDIA_ITEM_INTERFACE);
906
907         media_item_free(item);
908 }
909
910 static void media_folder_destroy(void *data)
911 {
912         struct media_folder *folder = data;
913
914         g_slist_free_full(folder->subfolders, media_folder_destroy);
915         g_slist_free_full(folder->items, media_item_destroy);
916
917         if (folder->msg != NULL)
918                 dbus_message_unref(folder->msg);
919
920         media_item_destroy(folder->item);
921         g_free(folder);
922 }
923
924 static void media_player_change_scope(struct media_player *mp,
925                                                 struct media_folder *folder)
926 {
927         if (mp->scope == folder)
928                 return;
929
930         DBG("%s", folder->item->name);
931
932         /* Skip setting current folder if folder is current playlist/search */
933         if (folder == mp->playlist || folder == mp->search)
934                 goto cleanup;
935
936         mp->folder = folder;
937
938         /* Skip item cleanup if scope is the current playlist */
939         if (mp->scope == mp->playlist)
940                 goto done;
941
942 cleanup:
943         g_slist_free_full(mp->scope->items, media_item_destroy);
944         mp->scope->items = NULL;
945
946         /* Destroy search folder if it exists and is not being set as scope */
947         if (mp->search != NULL && folder != mp->search) {
948                 mp->folders = g_slist_remove(mp->folders, mp->search);
949                 media_folder_destroy(mp->search);
950                 mp->search = NULL;
951         }
952
953 done:
954         mp->scope = folder;
955
956         g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
957                                 MEDIA_FOLDER_INTERFACE, "Name");
958         g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
959                                 MEDIA_FOLDER_INTERFACE, "NumberOfItems");
960 }
961
962 static struct media_folder *find_folder(GSList *folders, const char *pattern)
963 {
964         GSList *l;
965
966         for (l = folders; l; l = l->next) {
967                 struct media_folder *folder = l->data;
968
969                 if (g_str_equal(folder->item->name, pattern))
970                         return folder;
971
972                 if (g_str_equal(folder->item->path, pattern))
973                         return folder;
974
975                 folder = find_folder(folder->subfolders, pattern);
976                 if (folder != NULL)
977                         return folder;
978         }
979
980         return NULL;
981 }
982
983 static struct media_folder *media_player_find_folder(struct media_player *mp,
984                                                         const char *pattern)
985 {
986         return find_folder(mp->folders, pattern);
987 }
988
989 static DBusMessage *media_folder_change_folder(DBusConnection *conn,
990                                                 DBusMessage *msg, void *data)
991 {
992         struct media_player *mp = data;
993         struct media_folder *folder = mp->scope;
994         struct player_callback *cb = mp->cb;
995         const char *path;
996         int err;
997
998         if (!dbus_message_get_args(msg, NULL,
999                                         DBUS_TYPE_OBJECT_PATH, &path,
1000                                         DBUS_TYPE_INVALID))
1001                 return btd_error_failed(msg, strerror(EINVAL));
1002
1003         if (folder->msg != NULL)
1004                 return btd_error_failed(msg, strerror(EBUSY));
1005
1006         folder = media_player_find_folder(mp, path);
1007         if (folder == NULL)
1008                 return btd_error_failed(msg, strerror(EINVAL));
1009
1010         if (mp->scope == folder)
1011                 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1012
1013         if (folder == mp->playlist || folder == mp->folder ||
1014                                                 folder == mp->search) {
1015                 media_player_change_scope(mp, folder);
1016                 return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1017         }
1018
1019         if (cb->cbs->change_folder == NULL)
1020                 return btd_error_failed(msg, strerror(ENOTSUP));
1021
1022         err = cb->cbs->change_folder(mp, folder->item->name, folder->item->uid,
1023                                                                 cb->user_data);
1024         if (err < 0)
1025                 return btd_error_failed(msg, strerror(-err));
1026
1027         mp->scope->msg = dbus_message_ref(msg);
1028
1029         return NULL;
1030 }
1031
1032 static gboolean folder_name_exists(const GDBusPropertyTable *property,
1033                                                                 void *data)
1034 {
1035         struct media_player *mp = data;
1036         struct media_folder *folder = mp->scope;
1037
1038         if (folder == NULL || folder->item == NULL)
1039                 return FALSE;
1040
1041         return folder->item->name != NULL;
1042 }
1043
1044 static gboolean get_folder_name(const GDBusPropertyTable *property,
1045                                         DBusMessageIter *iter, void *data)
1046 {
1047         struct media_player *mp = data;
1048         struct media_folder *folder = mp->scope;
1049
1050         if (folder == NULL || folder->item == NULL)
1051                 return FALSE;
1052
1053         DBG("%s", folder->item->name);
1054
1055         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING,
1056                                                         &folder->item->name);
1057
1058         return TRUE;
1059 }
1060
1061 static gboolean items_exists(const GDBusPropertyTable *property, void *data)
1062 {
1063         struct media_player *mp = data;
1064
1065         return mp->scope != NULL;
1066 }
1067
1068 static gboolean get_items(const GDBusPropertyTable *property,
1069                                         DBusMessageIter *iter, void *data)
1070 {
1071         struct media_player *mp = data;
1072         struct media_folder *folder = mp->scope;
1073
1074         if (folder == NULL)
1075                 return FALSE;
1076
1077         DBG("%u", folder->number_of_items);
1078
1079         dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT32,
1080                                                 &folder->number_of_items);
1081
1082         return TRUE;
1083 }
1084
1085 static const GDBusMethodTable media_folder_methods[] = {
1086         { GDBUS_EXPERIMENTAL_ASYNC_METHOD("Search",
1087                         GDBUS_ARGS({ "string", "s" }, { "filter", "a{sv}" }),
1088                         GDBUS_ARGS({ "folder", "o" }),
1089                         media_folder_search) },
1090         { GDBUS_EXPERIMENTAL_ASYNC_METHOD("ListItems",
1091                         GDBUS_ARGS({ "filter", "a{sv}" }),
1092                         GDBUS_ARGS({ "items", "a{oa{sv}}" }),
1093                         media_folder_list_items) },
1094         { GDBUS_EXPERIMENTAL_ASYNC_METHOD("ChangeFolder",
1095                         GDBUS_ARGS({ "folder", "o" }), NULL,
1096                         media_folder_change_folder) },
1097         { }
1098 };
1099
1100 static const GDBusPropertyTable media_folder_properties[] = {
1101         { "Name", "s", get_folder_name, NULL, folder_name_exists,
1102                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1103         { "NumberOfItems", "u", get_items, NULL, items_exists,
1104                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1105         { }
1106 };
1107
1108 static void media_player_set_scope(struct media_player *mp,
1109                                                 struct media_folder *folder)
1110 {
1111         if (mp->scope == NULL) {
1112                 if (!g_dbus_register_interface(btd_get_dbus_connection(),
1113                                         mp->path, MEDIA_FOLDER_INTERFACE,
1114                                         media_folder_methods,
1115                                         NULL,
1116                                         media_folder_properties, mp, NULL)) {
1117                         error("D-Bus failed to register %s on %s path",
1118                                         MEDIA_FOLDER_INTERFACE, mp->path);
1119                         return;
1120                 }
1121                 mp->scope = folder;
1122                 return;
1123         }
1124
1125         return media_player_change_scope(mp, folder);
1126 }
1127
1128 void media_player_destroy(struct media_player *mp)
1129 {
1130         DBG("%s", mp->path);
1131
1132         g_dbus_unregister_interface(btd_get_dbus_connection(), mp->path,
1133                                                 MEDIA_PLAYER_INTERFACE);
1134
1135         if (mp->track)
1136                 g_hash_table_unref(mp->track);
1137
1138         if (mp->settings)
1139                 g_hash_table_unref(mp->settings);
1140
1141         if (mp->process_id > 0)
1142                 g_source_remove(mp->process_id);
1143
1144         if (mp->scope)
1145                 g_dbus_unregister_interface(btd_get_dbus_connection(),
1146                                                 mp->path,
1147                                                 MEDIA_FOLDER_INTERFACE);
1148
1149         g_slist_free_full(mp->pending, g_free);
1150         g_slist_free_full(mp->folders, media_folder_destroy);
1151
1152         g_timer_destroy(mp->progress);
1153         g_free(mp->cb);
1154         g_free(mp->status);
1155         g_free(mp->path);
1156         g_free(mp->device);
1157         g_free(mp->subtype);
1158         g_free(mp->type);
1159         g_free(mp->name);
1160         g_free(mp);
1161 }
1162
1163 struct media_player *media_player_controller_create(const char *path,
1164                                                                 uint16_t id)
1165 {
1166         struct media_player *mp;
1167
1168         mp = g_new0(struct media_player, 1);
1169         mp->device = g_strdup(path);
1170         mp->path = g_strdup_printf("%s/player%u", path, id);
1171         mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal,
1172                                                         g_free, g_free);
1173         mp->track = g_hash_table_new_full(g_str_hash, g_str_equal,
1174                                                         g_free, g_free);
1175         mp->progress = g_timer_new();
1176
1177         if (!g_dbus_register_interface(btd_get_dbus_connection(),
1178                                         mp->path, MEDIA_PLAYER_INTERFACE,
1179                                         media_player_methods,
1180                                         media_player_signals,
1181                                         media_player_properties, mp, NULL)) {
1182                 error("D-Bus failed to register %s path", mp->path);
1183                 media_player_destroy(mp);
1184                 return NULL;
1185         }
1186
1187         DBG("%s", mp->path);
1188
1189         return mp;
1190 }
1191
1192 void media_player_set_duration(struct media_player *mp, uint32_t duration)
1193 {
1194         char *value, *curval;
1195
1196         DBG("%u", duration);
1197
1198         /* Only update duration if track exists */
1199         if (g_hash_table_size(mp->track) == 0)
1200                 return;
1201
1202         /* Ignore if duration is already set */
1203         curval = g_hash_table_lookup(mp->track, "Duration");
1204         if (curval != NULL)
1205                 return;
1206
1207         value = g_strdup_printf("%u", duration);
1208
1209         g_hash_table_replace(mp->track, g_strdup("Duration"), value);
1210
1211         g_dbus_emit_property_changed(btd_get_dbus_connection(),
1212                                         mp->path, MEDIA_PLAYER_INTERFACE,
1213                                         "Track");
1214 }
1215
1216 void media_player_set_position(struct media_player *mp, uint32_t position)
1217 {
1218         DBG("%u", position);
1219
1220         /* Only update duration if track exists */
1221         if (g_hash_table_size(mp->track) == 0)
1222                 return;
1223
1224         mp->position = position;
1225         g_timer_start(mp->progress);
1226
1227         g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
1228                                         MEDIA_PLAYER_INTERFACE, "Position");
1229 }
1230
1231 void media_player_set_setting(struct media_player *mp, const char *key,
1232                                                         const char *value)
1233 {
1234         char *curval;
1235         struct pending_req *p;
1236
1237         DBG("%s: %s", key, value);
1238
1239         if (strcasecmp(key, "Error") == 0) {
1240                 p = g_slist_nth_data(mp->pending, 0);
1241                 if (p == NULL)
1242                         return;
1243
1244                 g_dbus_pending_property_error(p->id, ERROR_INTERFACE ".Failed",
1245                                                                         value);
1246                 goto send;
1247         }
1248
1249         curval = g_hash_table_lookup(mp->settings, key);
1250         if (g_strcmp0(curval, value) == 0)
1251                 goto done;
1252
1253         g_hash_table_replace(mp->settings, g_strdup(key), g_strdup(value));
1254         g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
1255                                         MEDIA_PLAYER_INTERFACE, key);
1256
1257 done:
1258         p = find_pending(mp, key);
1259         if (p == NULL)
1260                 return;
1261
1262         if (strcasecmp(value, p->value) == 0)
1263                 g_dbus_pending_property_success(p->id);
1264         else
1265                 g_dbus_pending_property_error(p->id,
1266                                         ERROR_INTERFACE ".NotSupported",
1267                                         "Operation is not supported");
1268
1269 send:
1270         mp->pending = g_slist_remove(mp->pending, p);
1271         g_free(p);
1272
1273         return;
1274 }
1275
1276 const char *media_player_get_status(struct media_player *mp)
1277 {
1278         return mp->status;
1279 }
1280
1281 void media_player_set_status(struct media_player *mp, const char *status)
1282 {
1283         DBG("%s", status);
1284
1285         if (g_strcmp0(mp->status, status) == 0)
1286                 return;
1287
1288         g_free(mp->status);
1289         mp->status = g_strdup(status);
1290
1291         g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
1292                                         MEDIA_PLAYER_INTERFACE, "Status");
1293
1294         mp->position = media_player_get_position(mp);
1295         g_timer_start(mp->progress);
1296 }
1297
1298 static gboolean process_metadata_changed(void *user_data)
1299 {
1300         struct media_player *mp = user_data;
1301         const char *item;
1302
1303         mp->process_id = 0;
1304
1305         g_dbus_emit_property_changed(btd_get_dbus_connection(),
1306                                         mp->path, MEDIA_PLAYER_INTERFACE,
1307                                         "Track");
1308
1309         item = g_hash_table_lookup(mp->track, "Item");
1310         if (item == NULL)
1311                 return FALSE;
1312
1313         g_dbus_emit_property_changed(btd_get_dbus_connection(),
1314                                         item, MEDIA_ITEM_INTERFACE,
1315                                         "Metadata");
1316
1317         return FALSE;
1318 }
1319
1320 void media_player_set_metadata(struct media_player *mp,
1321                                 struct media_item *item, const char *key,
1322                                 void *data, size_t len)
1323 {
1324         char *value, *curval;
1325
1326         value = g_strndup(data, len);
1327
1328         DBG("%s: %s", key, value);
1329
1330         curval = g_hash_table_lookup(mp->track, key);
1331         if (g_strcmp0(curval, value) == 0) {
1332                 g_free(value);
1333                 return;
1334         }
1335
1336         if (mp->process_id == 0) {
1337                 g_hash_table_remove_all(mp->track);
1338                 mp->process_id = g_idle_add(process_metadata_changed, mp);
1339         }
1340
1341         g_hash_table_replace(mp->track, g_strdup(key), value);
1342 }
1343
1344 void media_player_set_type(struct media_player *mp, const char *type)
1345 {
1346         if (g_strcmp0(mp->type, type) == 0)
1347                 return;
1348
1349         DBG("%s", type);
1350
1351         mp->type = g_strdup(type);
1352
1353         g_dbus_emit_property_changed(btd_get_dbus_connection(),
1354                                         mp->path, MEDIA_PLAYER_INTERFACE,
1355                                         "Type");
1356 }
1357
1358 void media_player_set_subtype(struct media_player *mp, const char *subtype)
1359 {
1360         if (g_strcmp0(mp->subtype, subtype) == 0)
1361                 return;
1362
1363         DBG("%s", subtype);
1364
1365         mp->subtype = g_strdup(subtype);
1366
1367         g_dbus_emit_property_changed(btd_get_dbus_connection(),
1368                                         mp->path, MEDIA_PLAYER_INTERFACE,
1369                                         "Subtype");
1370 }
1371
1372 void media_player_set_name(struct media_player *mp, const char *name)
1373 {
1374         if (g_strcmp0(mp->name, name) == 0)
1375                 return;
1376
1377         DBG("%s", name);
1378
1379         mp->name = g_strdup(name);
1380
1381         g_dbus_emit_property_changed(btd_get_dbus_connection(),
1382                                         mp->path, MEDIA_PLAYER_INTERFACE,
1383                                         "Name");
1384 }
1385
1386 void media_player_set_browsable(struct media_player *mp, bool enabled)
1387 {
1388         if (mp->browsable == enabled)
1389                 return;
1390
1391         DBG("%s", enabled ? "true" : "false");
1392
1393         mp->browsable = enabled;
1394
1395         g_dbus_emit_property_changed(btd_get_dbus_connection(),
1396                                         mp->path, MEDIA_PLAYER_INTERFACE,
1397                                         "Browsable");
1398 }
1399
1400 void media_player_set_searchable(struct media_player *mp, bool enabled)
1401 {
1402         if (mp->browsable == enabled)
1403                 return;
1404
1405         DBG("%s", enabled ? "true" : "false");
1406
1407         mp->searchable = enabled;
1408
1409         g_dbus_emit_property_changed(btd_get_dbus_connection(),
1410                                         mp->path, MEDIA_PLAYER_INTERFACE,
1411                                         "Searchable");
1412 }
1413
1414 void media_player_set_folder(struct media_player *mp, const char *name,
1415                                                 uint32_t number_of_items)
1416 {
1417         struct media_folder *folder;
1418
1419         DBG("%s number of items %u", name, number_of_items);
1420
1421         folder = media_player_find_folder(mp, name);
1422         if (folder == NULL) {
1423                 error("Unknown folder: %s", name);
1424                 return;
1425         }
1426
1427         folder->number_of_items = number_of_items;
1428
1429         media_player_set_scope(mp, folder);
1430 }
1431
1432 void media_player_set_playlist(struct media_player *mp, const char *name)
1433 {
1434         struct media_folder *folder;
1435
1436         DBG("%s", name);
1437
1438         folder = media_player_find_folder(mp, name);
1439         if (folder == NULL) {
1440                 error("Unknown folder: %s", name);
1441                 return;
1442         }
1443
1444         mp->playlist = folder;
1445
1446         g_dbus_emit_property_changed(btd_get_dbus_connection(), mp->path,
1447                                         MEDIA_PLAYER_INTERFACE, "Playlist");
1448 }
1449
1450 static struct media_item *media_folder_find_item(struct media_folder *folder,
1451                                                                 uint64_t uid)
1452 {
1453         GSList *l;
1454
1455         if (uid == 0)
1456                 return NULL;
1457
1458         for (l = folder->items; l; l = l->next) {
1459                 struct media_item *item = l->data;
1460
1461                 if (item->uid == uid)
1462                         return item;
1463         }
1464
1465         return NULL;
1466 }
1467
1468 static DBusMessage *media_item_play(DBusConnection *conn, DBusMessage *msg,
1469                                                                 void *data)
1470 {
1471         struct media_item *item = data;
1472         struct media_player *mp = item->player;
1473         struct player_callback *cb = mp->cb;
1474         int err;
1475
1476         if (!item->playable)
1477                 return btd_error_failed(msg, strerror(ENOTSUP));
1478
1479         if (cb->cbs->play_item == NULL)
1480                 return btd_error_failed(msg, strerror(ENOTSUP));
1481
1482         err = cb->cbs->play_item(mp, item->path, item->uid, cb->user_data);
1483         if (err < 0)
1484                 return btd_error_failed(msg, strerror(-err));
1485
1486         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1487 }
1488
1489 static DBusMessage *media_item_add_to_nowplaying(DBusConnection *conn,
1490                                                 DBusMessage *msg, void *data)
1491 {
1492         struct media_item *item = data;
1493         struct media_player *mp = item->player;
1494         struct player_callback *cb = mp->cb;
1495         int err;
1496
1497         if (!item->playable)
1498                 return btd_error_failed(msg, strerror(ENOTSUP));
1499
1500         if (cb->cbs->play_item == NULL)
1501                 return btd_error_failed(msg, strerror(ENOTSUP));
1502
1503         err = cb->cbs->add_to_nowplaying(mp, item->path, item->uid,
1504                                                         cb->user_data);
1505         if (err < 0)
1506                 return btd_error_failed(msg, strerror(-err));
1507
1508         return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
1509 }
1510
1511 static gboolean get_player(const GDBusPropertyTable *property,
1512                                         DBusMessageIter *iter, void *data)
1513 {
1514         struct media_item *item = data;
1515
1516         DBG("%s", item->player->path);
1517
1518         dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH,
1519                                                         &item->player->path);
1520
1521         return TRUE;
1522 }
1523
1524 static gboolean item_name_exists(const GDBusPropertyTable *property,
1525                                                                 void *data)
1526 {
1527         struct media_item *item = data;
1528
1529         return item->name != NULL;
1530 }
1531
1532 static gboolean get_item_name(const GDBusPropertyTable *property,
1533                                         DBusMessageIter *iter, void *data)
1534 {
1535         struct media_item *item = data;
1536
1537         if (item->name == NULL)
1538                 return FALSE;
1539
1540         DBG("%s", item->name);
1541
1542         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &item->name);
1543
1544         return TRUE;
1545 }
1546
1547 static const char *type_to_string(uint8_t type)
1548 {
1549         switch (type) {
1550         case PLAYER_ITEM_TYPE_AUDIO:
1551                 return "audio";
1552         case PLAYER_ITEM_TYPE_VIDEO:
1553                 return "video";
1554         case PLAYER_ITEM_TYPE_FOLDER:
1555                 return "folder";
1556         }
1557
1558         return NULL;
1559 }
1560
1561 static gboolean get_item_type(const GDBusPropertyTable *property,
1562                                         DBusMessageIter *iter, void *data)
1563 {
1564         struct media_item *item = data;
1565         const char *string;
1566
1567         string = type_to_string(item->type);
1568
1569         DBG("%s", string);
1570
1571         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);
1572
1573         return TRUE;
1574 }
1575
1576 static gboolean get_playable(const GDBusPropertyTable *property,
1577                                         DBusMessageIter *iter, void *data)
1578 {
1579         struct media_item *item = data;
1580         dbus_bool_t value;
1581
1582         DBG("%s", item->playable ? "true" : "false");
1583
1584         value = item->playable;
1585
1586         dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &value);
1587
1588         return TRUE;
1589 }
1590
1591 static const char *folder_type_to_string(uint8_t type)
1592 {
1593         switch (type) {
1594         case PLAYER_FOLDER_TYPE_MIXED:
1595                 return "mixed";
1596         case PLAYER_FOLDER_TYPE_TITLES:
1597                 return "titles";
1598         case PLAYER_FOLDER_TYPE_ALBUMS:
1599                 return "albums";
1600         case PLAYER_FOLDER_TYPE_ARTISTS:
1601                 return "artists";
1602         case PLAYER_FOLDER_TYPE_GENRES:
1603                 return "genres";
1604         case PLAYER_FOLDER_TYPE_PLAYLISTS:
1605                 return "playlists";
1606         case PLAYER_FOLDER_TYPE_YEARS:
1607                 return "years";
1608         }
1609
1610         return NULL;
1611 }
1612
1613 static gboolean folder_type_exists(const GDBusPropertyTable *property,
1614                                                                 void *data)
1615 {
1616         struct media_item *item = data;
1617
1618         return folder_type_to_string(item->folder_type) != NULL;
1619 }
1620
1621 static gboolean get_folder_type(const GDBusPropertyTable *property,
1622                                         DBusMessageIter *iter, void *data)
1623 {
1624         struct media_item *item = data;
1625         const char *string;
1626
1627         string = folder_type_to_string(item->folder_type);
1628         if (string == NULL)
1629                 return FALSE;
1630
1631         DBG("%s", string);
1632
1633         dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &string);
1634
1635         return TRUE;
1636 }
1637
1638 static gboolean metadata_exists(const GDBusPropertyTable *property, void *data)
1639 {
1640         struct media_item *item = data;
1641
1642         return item->metadata != NULL;
1643 }
1644
1645 static void append_metadata(void *key, void *value, void *user_data)
1646 {
1647         DBusMessageIter *dict = user_data;
1648         const char *strkey = key;
1649
1650         if (strcasecmp(strkey, "Item") == 0)
1651                 return;
1652
1653         if (strcasecmp(strkey, "Duration") == 0 ||
1654                         strcasecmp(strkey, "TrackNumber") == 0 ||
1655                         strcasecmp(strkey, "NumberOfTracks") == 0)  {
1656                 uint32_t num = atoi(value);
1657                 dict_append_entry(dict, key, DBUS_TYPE_UINT32, &num);
1658         } else {
1659                 dict_append_entry(dict, key, DBUS_TYPE_STRING, &value);
1660         }
1661 }
1662
1663 static gboolean get_metadata(const GDBusPropertyTable *property,
1664                                         DBusMessageIter *iter, void *data)
1665 {
1666         struct media_item *item = data;
1667         DBusMessageIter dict;
1668
1669         dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY,
1670                                         DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
1671                                         DBUS_TYPE_STRING_AS_STRING
1672                                         DBUS_TYPE_VARIANT_AS_STRING
1673                                         DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
1674                                         &dict);
1675
1676         if (g_hash_table_size(item->metadata) > 0)
1677                 g_hash_table_foreach(item->metadata, append_metadata, &dict);
1678         else if (item->name != NULL)
1679                 dict_append_entry(&dict, "Title", DBUS_TYPE_STRING,
1680                                                                 &item->name);
1681
1682         dbus_message_iter_close_container(iter, &dict);
1683
1684         return TRUE;
1685 }
1686
1687 static const GDBusMethodTable media_item_methods[] = {
1688         { GDBUS_EXPERIMENTAL_METHOD("Play", NULL, NULL,
1689                                         media_item_play) },
1690         { GDBUS_EXPERIMENTAL_METHOD("AddtoNowPlaying", NULL, NULL,
1691                                         media_item_add_to_nowplaying) },
1692         { }
1693 };
1694
1695 static const GDBusPropertyTable media_item_properties[] = {
1696         { "Player", "o", get_player, NULL, NULL,
1697                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1698         { "Name", "s", get_item_name, NULL, item_name_exists,
1699                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1700         { "Type", "s", get_item_type, NULL, NULL,
1701                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1702         { "FolderType", "s", get_folder_type, NULL, folder_type_exists,
1703                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1704         { "Playable", "b", get_playable, NULL, NULL,
1705                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1706         { "Metadata", "a{sv}", get_metadata, NULL, metadata_exists,
1707                                         G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
1708         { }
1709 };
1710
1711 void media_item_set_playable(struct media_item *item, bool value)
1712 {
1713         if (item->playable == value)
1714                 return;
1715
1716         item->playable = value;
1717
1718         g_dbus_emit_property_changed(btd_get_dbus_connection(), item->path,
1719                                         MEDIA_ITEM_INTERFACE, "Playable");
1720 }
1721
1722 static struct media_item *media_folder_create_item(struct media_player *mp,
1723                                                 struct media_folder *folder,
1724                                                 const char *name,
1725                                                 player_item_type_t type,
1726                                                 uint64_t uid)
1727 {
1728         struct media_item *item;
1729         const char *strtype;
1730
1731         item = media_folder_find_item(folder, uid);
1732         if (item != NULL)
1733                 return item;
1734
1735         strtype = type_to_string(type);
1736         if (strtype == NULL)
1737                 return NULL;
1738
1739         DBG("%s type %s uid %" PRIu64 "", name, strtype, uid);
1740
1741         item = g_new0(struct media_item, 1);
1742         item->player = mp;
1743         item->uid = uid;
1744
1745         if (uid > 0)
1746                 item->path = g_strdup_printf("%s/item%" PRIu64 "",
1747                                                 folder->item->path, uid);
1748         else
1749                 item->path = g_strdup_printf("%s%s", mp->path, name);
1750
1751         item->name = g_strdup(name);
1752         item->type = type;
1753         item->folder_type = PLAYER_FOLDER_TYPE_INVALID;
1754
1755         if (!g_dbus_register_interface(btd_get_dbus_connection(),
1756                                         item->path, MEDIA_ITEM_INTERFACE,
1757                                         media_item_methods,
1758                                         NULL,
1759                                         media_item_properties, item, NULL)) {
1760                 error("D-Bus failed to register %s on %s path",
1761                                         MEDIA_ITEM_INTERFACE, item->path);
1762                 media_item_free(item);
1763                 return NULL;
1764         }
1765
1766         if (type != PLAYER_ITEM_TYPE_FOLDER) {
1767                 folder->items = g_slist_prepend(folder->items, item);
1768                 item->metadata = g_hash_table_new_full(g_str_hash, g_str_equal,
1769                                                         g_free, g_free);
1770         }
1771
1772         DBG("%s", item->path);
1773
1774         return item;
1775 }
1776
1777 struct media_item *media_player_create_item(struct media_player *mp,
1778                                                 const char *name,
1779                                                 player_item_type_t type,
1780                                                 uint64_t uid)
1781 {
1782         return media_folder_create_item(mp, mp->scope, name, type, uid);
1783 }
1784
1785 static struct media_folder *
1786 media_player_find_folder_by_uid(struct media_player *mp, uint64_t uid)
1787 {
1788         struct media_folder *folder = mp->scope;
1789         GSList *l;
1790
1791         for (l = folder->subfolders; l; l = l->next) {
1792                 struct media_folder *folder = l->data;
1793
1794                 if (folder->item->uid == uid)
1795                         return folder;
1796         }
1797
1798         return NULL;
1799 }
1800
1801 struct media_item *media_player_create_folder(struct media_player *mp,
1802                                                 const char *name,
1803                                                 player_folder_type_t type,
1804                                                 uint64_t uid)
1805 {
1806         struct media_folder *folder;
1807         struct media_item *item;
1808
1809         if (uid > 0)
1810                 folder = media_player_find_folder_by_uid(mp, uid);
1811         else
1812                 folder = media_player_find_folder(mp, name);
1813
1814         if (folder != NULL)
1815                 return folder->item;
1816
1817         if (uid > 0)
1818                 item = media_player_create_subfolder(mp, name, uid);
1819         else
1820                 item = media_player_create_item(mp, name,
1821                                                 PLAYER_ITEM_TYPE_FOLDER, uid);
1822
1823         if (item == NULL)
1824                 return NULL;
1825
1826         folder = g_new0(struct media_folder, 1);
1827         folder->item = item;
1828
1829         item->folder_type = type;
1830
1831         if (mp->folder != NULL)
1832                 goto done;
1833
1834         mp->folder = folder;
1835
1836 done:
1837         if (uid > 0) {
1838                 folder->parent = mp->folder;
1839                 mp->folder->subfolders = g_slist_prepend(
1840                                                         mp->folder->subfolders,
1841                                                         folder);
1842         } else
1843                 mp->folders = g_slist_prepend(mp->folders, folder);
1844
1845         return item;
1846 }
1847
1848 void media_player_set_callbacks(struct media_player *mp,
1849                                 const struct media_player_callback *cbs,
1850                                 void *user_data)
1851 {
1852         struct player_callback *cb;
1853
1854         if (mp->cb)
1855                 g_free(mp->cb);
1856
1857         cb = g_new0(struct player_callback, 1);
1858         cb->cbs = cbs;
1859         cb->user_data = user_data;
1860
1861         mp->cb = cb;
1862 }
1863
1864 struct media_item *media_player_set_playlist_item(struct media_player *mp,
1865                                                                 uint64_t uid)
1866 {
1867         struct media_folder *folder = mp->playlist;
1868         struct media_item *item;
1869
1870         DBG("%" PRIu64 "", uid);
1871
1872         if (folder == NULL || uid == 0)
1873                 return NULL;
1874
1875         item = media_folder_create_item(mp, folder, NULL,
1876                                                 PLAYER_ITEM_TYPE_AUDIO, uid);
1877         if (item == NULL)
1878                 return NULL;
1879
1880         media_item_set_playable(item, true);
1881
1882         if (mp->track != item->metadata) {
1883                 g_hash_table_unref(mp->track);
1884                 mp->track = g_hash_table_ref(item->metadata);
1885         }
1886
1887         if (item == g_hash_table_lookup(mp->track, "Item"))
1888                 return item;
1889
1890         if (mp->process_id == 0) {
1891                 g_hash_table_remove_all(mp->track);
1892                 mp->process_id = g_idle_add(process_metadata_changed, mp);
1893         }
1894
1895         g_hash_table_replace(mp->track, g_strdup("Item"),
1896                                                 g_strdup(item->path));
1897
1898         return item;
1899 }