Code

mpdclient: added property "events", emulate idle
[ncmpc.git] / src / mpdclient.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2009 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
20 #include "mpdclient.h"
21 #include "filelist.h"
22 #include "screen_client.h"
23 #include "config.h"
24 #include "options.h"
25 #include "strfsong.h"
26 #include "utils.h"
28 #include <mpd/client.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <string.h>
35 #undef  ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD /* broken with song id's */
36 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
37 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
39 #define BUFSIZE 1024
41 static bool
42 MPD_ERROR(const struct mpdclient *client)
43 {
44         return client->connection == NULL ||
45                 mpd_connection_get_error(client->connection) != MPD_ERROR_SUCCESS;
46 }
48 /* filelist sorting functions */
49 static gint
50 compare_filelistentry(gconstpointer filelist_entry1,
51                           gconstpointer filelist_entry2)
52 {
53         const struct mpd_entity *e1, *e2;
54         int n = 0;
56         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
57         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
59         if (e1 != NULL && e2 != NULL &&
60             mpd_entity_get_type(e1) == mpd_entity_get_type(e2)) {
61                 switch (mpd_entity_get_type(e1)) {
62                 case MPD_ENTITY_TYPE_UNKNOWN:
63                         break;
64                 case MPD_ENTITY_TYPE_DIRECTORY:
65                         n = g_utf8_collate(mpd_directory_get_path(mpd_entity_get_directory(e1)),
66                                            mpd_directory_get_path(mpd_entity_get_directory(e2)));
67                         break;
68                 case MPD_ENTITY_TYPE_SONG:
69                         break;
70                 case MPD_ENTITY_TYPE_PLAYLIST:
71                         n = g_utf8_collate(mpd_playlist_get_path(mpd_entity_get_playlist(e1)),
72                                            mpd_playlist_get_path(mpd_entity_get_playlist(e2)));
73                 }
74         }
75         return n;
76 }
78 /* sort by list-format */
79 gint
80 compare_filelistentry_format(gconstpointer filelist_entry1,
81                              gconstpointer filelist_entry2)
82 {
83         const struct mpd_entity *e1, *e2;
84         char key1[BUFSIZE], key2[BUFSIZE];
85         int n = 0;
87         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
88         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
90         if (e1 && e2 &&
91             mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
92             mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
93                 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
94                 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
95                 n = strcmp(key1,key2);
96         }
98         return n;
99 }
102 /****************************************************************************/
103 /*** mpdclient functions ****************************************************/
104 /****************************************************************************/
106 gint
107 mpdclient_handle_error(struct mpdclient *c)
109         enum mpd_error error = mpd_connection_get_error(c->connection);
111         assert(error != MPD_ERROR_SUCCESS);
113         if (error == MPD_ERROR_SERVER &&
114             mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
115             screen_auth(c))
116                 return 0;
118         if (error == MPD_ERROR_SERVER)
119                 error = error | (mpd_connection_get_server_error(c->connection) << 8);
121         mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
123         if (!mpd_connection_clear_error(c->connection))
124                 mpdclient_disconnect(c);
126         return error;
129 static gint
130 mpdclient_finish_command(struct mpdclient *c)
132         return mpd_response_finish(c->connection)
133                 ? 0 : mpdclient_handle_error(c);
136 struct mpdclient *
137 mpdclient_new(void)
139         struct mpdclient *c;
141         c = g_new0(struct mpdclient, 1);
142         playlist_init(&c->playlist);
143         c->volume = -1;
144         c->events = 0;
146         return c;
149 void
150 mpdclient_free(struct mpdclient *c)
152         mpdclient_disconnect(c);
154         mpdclient_playlist_free(&c->playlist);
156         g_list_free(c->playlist_callbacks);
157         g_list_free(c->browse_callbacks);
158         g_free(c);
161 void
162 mpdclient_disconnect(struct mpdclient *c)
164         if (c->connection)
165                 mpd_connection_free(c->connection);
166         c->connection = NULL;
168         if (c->status)
169                 mpd_status_free(c->status);
170         c->status = NULL;
172         playlist_clear(&c->playlist);
174         if (c->song)
175                 c->song = NULL;
178 bool
179 mpdclient_connect(struct mpdclient *c,
180                   const gchar *host,
181                   gint port,
182                   gfloat _timeout,
183                   const gchar *password)
185         /* close any open connection */
186         if( c->connection )
187                 mpdclient_disconnect(c);
189         /* connect to MPD */
190         c->connection = mpd_connection_new(host, port, _timeout * 1000);
191         if (c->connection == NULL)
192                 g_error("Out of memory");
194         if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
195                 mpdclient_handle_error(c);
196                 mpdclient_disconnect(c);
197                 return false;
198         }
200         /* send password */
201         if (password != NULL && !mpd_run_password(c->connection, password)) {
202                 mpdclient_handle_error(c);
203                 mpdclient_disconnect(c);
204                 return false;
205         }
207         return true;
210 bool
211 mpdclient_update(struct mpdclient *c)
213         bool retval;
215         c->volume = -1;
217         if (MPD_ERROR(c))
218                 return false;
220         /* always announce these options as long as we don't have real
221            "idle" support */
222         c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
224         /* free the old status */
225         if (c->status)
226                 mpd_status_free(c->status);
228         /* retrieve new status */
229         c->status = mpd_run_status(c->connection);
230         if (c->status == NULL)
231                 return mpdclient_handle_error(c) == 0;
233         if (c->update_id != mpd_status_get_update_id(c->status)) {
234                 c->events |= MPD_IDLE_UPDATE;
236                 if (c->update_id > 0)
237                         c->events |= MPD_IDLE_DATABASE;
238         }
240         if (c->update_id > 0 &&
241             c->update_id != mpd_status_get_update_id(c->status))
242                 mpdclient_browse_callback(c, BROWSE_DB_UPDATED, NULL);
244         c->update_id = mpd_status_get_update_id(c->status);
246         if (c->volume != mpd_status_get_volume(c->status))
247                 c->events |= MPD_IDLE_MIXER;
249         c->volume = mpd_status_get_volume(c->status);
251         /* check if the playlist needs an update */
252         if (c->playlist.id != mpd_status_get_queue_version(c->status)) {
253                 c->events |= MPD_IDLE_PLAYLIST;
255                 if (!playlist_is_empty(&c->playlist))
256                         retval = mpdclient_playlist_update_changes(c);
257                 else
258                         retval = mpdclient_playlist_update(c);
259         } else
260                 retval = true;
262         /* update the current song */
263         if (!c->song || mpd_status_get_song_id(c->status)) {
264                 c->song = playlist_get_song(&c->playlist,
265                                             mpd_status_get_song_pos(c->status));
266         }
268         return retval;
272 /****************************************************************************/
273 /*** MPD Commands  **********************************************************/
274 /****************************************************************************/
276 gint
277 mpdclient_cmd_play(struct mpdclient *c, gint idx)
279         const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
281         if (MPD_ERROR(c))
282                 return -1;
284         if (song)
285                 mpd_send_play_id(c->connection, mpd_song_get_id(song));
286         else
287                 mpd_send_play(c->connection);
289         return mpdclient_finish_command(c);
292 gint
293 mpdclient_cmd_crop(struct mpdclient *c)
295         struct mpd_status *status;
296         bool playing;
297         int length, current;
299         if (MPD_ERROR(c))
300                 return -1;
302         status = mpd_run_status(c->connection);
303         if (status == NULL)
304                 return mpdclient_handle_error(c);
306         playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
307                 mpd_status_get_state(status) == MPD_STATE_PAUSE;
308         length = mpd_status_get_queue_length(status);
309         current = mpd_status_get_song_pos(status);
311         mpd_status_free(status);
313         if (!playing || length < 2)
314                 return 0;
316         mpd_command_list_begin(c->connection, false);
318         while (--length >= 0)
319                 if (length != current)
320                         mpd_send_delete(c->connection, length);
322         mpd_command_list_end(c->connection);
324         return mpdclient_finish_command(c);
327 gint
328 mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end)
330         mpd_send_shuffle_range(c->connection, start, end);
331         return mpdclient_finish_command(c);
334 gint
335 mpdclient_cmd_clear(struct mpdclient *c)
337         gint retval = 0;
339         if (MPD_ERROR(c))
340                 return -1;
342         mpd_send_clear(c->connection);
343         retval = mpdclient_finish_command(c);
344         /* call playlist updated callback */
345         mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL);
346         return retval;
349 gint
350 mpdclient_cmd_volume(struct mpdclient *c, gint value)
352         if (MPD_ERROR(c))
353                 return -1;
355         mpd_send_set_volume(c->connection, value);
356         return mpdclient_finish_command(c);
359 gint mpdclient_cmd_volume_up(struct mpdclient *c)
361         if (MPD_ERROR(c))
362                 return -1;
364         if (c->status == NULL ||
365             mpd_status_get_volume(c->status) == -1)
366                 return 0;
368         if (c->volume < 0)
369                 c->volume = mpd_status_get_volume(c->status);
371         if (c->volume >= 100)
372                 return 0;
374         return mpdclient_cmd_volume(c, ++c->volume);
377 gint mpdclient_cmd_volume_down(struct mpdclient *c)
379         if (MPD_ERROR(c))
380                 return -1;
382         if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
383                 return 0;
385         if (c->volume < 0)
386                 c->volume = mpd_status_get_volume(c->status);
388         if (c->volume <= 0)
389                 return 0;
391         return mpdclient_cmd_volume(c, --c->volume);
394 gint
395 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
397         if (MPD_ERROR(c))
398                 return -1;
400         mpd_send_add(c->connection, path_utf8);
401         return mpdclient_finish_command(c);
404 gint
405 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
407         gint retval = 0;
409         if (MPD_ERROR(c))
410                 return -1;
412         if (song == NULL)
413                 return -1;
415         /* send the add command to mpd */
416         mpd_send_add(c->connection, mpd_song_get_uri(song));
417         if( (retval=mpdclient_finish_command(c)) )
418                 return retval;
420 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
421         /* add the song to playlist */
422         playlist_append(&c->playlist, song);
424         /* increment the playlist id, so we don't retrieve a new playlist */
425         c->playlist.id++;
427         /* call playlist updated callback */
428         mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
429 #endif
431         return 0;
434 gint
435 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
437         gint retval = 0;
438         struct mpd_song *song;
440         if (MPD_ERROR(c))
441                 return -1;
443         if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
444                 return -1;
446         song = playlist_get(&c->playlist, idx);
448         /* send the delete command to mpd */
449         mpd_send_delete_id(c->connection, mpd_song_get_id(song));
450         if( (retval=mpdclient_finish_command(c)) )
451                 return retval;
453 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
454         /* increment the playlist id, so we don't retrieve a new playlist */
455         c->playlist.id++;
457         /* remove the song from the playlist */
458         playlist_remove_reuse(&c->playlist, idx);
460         /* call playlist updated callback */
461         mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song);
463         /* remove references to the song */
464         if (c->song == song)
465                 c->song = NULL;
467         mpd_song_free(song);
468 #endif
470         return 0;
473 gint
474 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
476         gint n;
477         struct mpd_song *song1, *song2;
479         if (MPD_ERROR(c))
480                 return -1;
482         if (old_index == new_index || new_index < 0 ||
483             (guint)new_index >= c->playlist.list->len)
484                 return -1;
486         song1 = playlist_get(&c->playlist, old_index);
487         song2 = playlist_get(&c->playlist, new_index);
489         /* send the move command to mpd */
490         mpd_send_swap_id(c->connection,
491                          mpd_song_get_id(song1), mpd_song_get_id(song2));
492         if( (n=mpdclient_finish_command(c)) )
493                 return n;
495 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
496         /* update the playlist */
497         playlist_swap(&c->playlist, old_index, new_index);
499         /* increment the playlist id, so we don't retrieve a new playlist */
500         c->playlist.id++;
501 #endif
503         /* call playlist updated callback */
504         mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index);
506         return 0;
509 gint
510 mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8)
512         gint retval = 0;
514         if (MPD_ERROR(c))
515                 return -1;
517         mpd_send_save(c->connection, filename_utf8);
518         if ((retval = mpdclient_finish_command(c)) == 0) {
519                 mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL);
520                 c->events |= MPD_IDLE_STORED_PLAYLIST;
521         }
523         return retval;
526 gint
527 mpdclient_cmd_load_playlist(struct mpdclient *c, const gchar *filename_utf8)
529         if (MPD_ERROR(c))
530                 return -1;
532         mpd_send_load(c->connection, filename_utf8);
533         return mpdclient_finish_command(c);
536 gint
537 mpdclient_cmd_delete_playlist(struct mpdclient *c, const gchar *filename_utf8)
539         gint retval = 0;
541         if (MPD_ERROR(c))
542                 return -1;
544         mpd_send_rm(c->connection, filename_utf8);
545         if ((retval = mpdclient_finish_command(c)) == 0) {
546                 mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL);
547                 c->events |= MPD_IDLE_STORED_PLAYLIST;
548         }
550         return retval;
554 /****************************************************************************/
555 /*** Callback management functions ******************************************/
556 /****************************************************************************/
558 static void
559 do_list_callbacks(struct mpdclient *c, GList *list, gint event, gpointer data)
561         while (list) {
562                 mpdc_list_cb_t fn = list->data;
564                 fn(c, event, data);
565                 list = list->next;
566         }
569 void
570 mpdclient_playlist_callback(struct mpdclient *c, int event, gpointer data)
572         do_list_callbacks(c, c->playlist_callbacks, event, data);
575 void
576 mpdclient_install_playlist_callback(struct mpdclient *c,mpdc_list_cb_t cb)
578         c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb);
581 void
582 mpdclient_remove_playlist_callback(struct mpdclient *c, mpdc_list_cb_t cb)
584         c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb);
587 void
588 mpdclient_browse_callback(struct mpdclient *c, int event, gpointer data)
590         do_list_callbacks(c, c->browse_callbacks, event, data);
594 void
595 mpdclient_install_browse_callback(struct mpdclient *c,mpdc_list_cb_t cb)
597         c->browse_callbacks = g_list_append(c->browse_callbacks, cb);
600 void
601 mpdclient_remove_browse_callback(struct mpdclient *c, mpdc_list_cb_t cb)
603         c->browse_callbacks = g_list_remove(c->browse_callbacks, cb);
607 /****************************************************************************/
608 /*** Playlist management functions ******************************************/
609 /****************************************************************************/
611 /* update playlist */
612 bool
613 mpdclient_playlist_update(struct mpdclient *c)
615         struct mpd_entity *entity;
617         if (MPD_ERROR(c))
618                 return false;
620         playlist_clear(&c->playlist);
622         mpd_send_list_queue_meta(c->connection);
623         while ((entity = mpd_recv_entity(c->connection))) {
624                 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
625                         playlist_append(&c->playlist, mpd_entity_get_song(entity));
627                 mpd_entity_free(entity);
628         }
630         c->playlist.id = mpd_status_get_queue_version(c->status);
631         c->song = NULL;
633         /* call playlist updated callbacks */
634         mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
636         return mpdclient_finish_command(c) == 0;
639 /* update playlist (plchanges) */
640 bool
641 mpdclient_playlist_update_changes(struct mpdclient *c)
643         struct mpd_song *song;
644         guint length;
646         if (MPD_ERROR(c))
647                 return false;
649         mpd_send_queue_changes_meta(c->connection, c->playlist.id);
651         while ((song = mpd_recv_song(c->connection)) != NULL) {
652                 int pos = mpd_song_get_pos(song);
654                 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
655                         /* update song */
656                         playlist_replace(&c->playlist, pos, song);
657                 } else {
658                         /* add a new song */
659                         playlist_append(&c->playlist, song);
660                 }
662                 mpd_song_free(song);
663         }
665         /* remove trailing songs */
667         length = mpd_status_get_queue_length(c->status);
668         while (length < c->playlist.list->len) {
669                 guint pos = c->playlist.list->len - 1;
671                 /* Remove the last playlist entry */
672                 playlist_remove(&c->playlist, pos);
673         }
675         c->song = NULL;
676         c->playlist.id = mpd_status_get_queue_version(c->status);
678         mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
680         return mpdclient_finish_command(c) == 0;
684 /****************************************************************************/
685 /*** Filelist functions *****************************************************/
686 /****************************************************************************/
688 struct filelist *
689 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
691         struct filelist *filelist;
692         struct mpd_entity *entity;
694         if (MPD_ERROR(c))
695                 return NULL;
697         mpd_send_list_meta(c->connection, path);
698         filelist = filelist_new();
700         while ((entity = mpd_recv_entity(c->connection)) != NULL)
701                 filelist_append(filelist, entity);
703         if (mpdclient_finish_command(c)) {
704                 filelist_free(filelist);
705                 return NULL;
706         }
708         filelist_sort_dir_play(filelist, compare_filelistentry);
710         return filelist;
713 static struct filelist *
714 mpdclient_recv_filelist_response(struct mpdclient *c)
716         struct filelist *filelist;
717         struct mpd_entity *entity;
719         filelist = filelist_new();
721         while ((entity = mpd_recv_entity(c->connection)) != NULL)
722                 filelist_append(filelist, entity);
724         if (mpdclient_finish_command(c)) {
725                 filelist_free(filelist);
726                 return NULL;
727         }
729         return filelist;
732 struct filelist *
733 mpdclient_filelist_search(struct mpdclient *c,
734                           int exact_match,
735                           enum mpd_tag_type tag,
736                           gchar *filter_utf8)
738         if (MPD_ERROR(c))
739                 return NULL;
741         mpd_search_db_songs(c->connection, exact_match);
742         mpd_search_add_tag_constraint(c->connection, MPD_OPERATOR_DEFAULT,
743                                       tag, filter_utf8);
744         mpd_search_commit(c->connection);
746         return mpdclient_recv_filelist_response(c);
749 int
750 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
752         guint i;
754         if (MPD_ERROR(c))
755                 return -1;
757         if (filelist_is_empty(fl))
758                 return 0;
760         mpd_command_list_begin(c->connection, false);
762         for (i = 0; i < filelist_length(fl); ++i) {
763                 struct filelist_entry *entry = filelist_get(fl, i);
764                 struct mpd_entity *entity  = entry->entity;
766                 if (entity != NULL &&
767                     mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
768                         const struct mpd_song *song =
769                                 mpd_entity_get_song(entity);
770                         const char *uri = mpd_song_get_uri(song);
772                         if (uri != NULL)
773                                 mpd_send_add(c->connection, uri);
774                 }
775         }
777         mpd_command_list_end(c->connection);
778         return mpdclient_finish_command(c);
781 GList *
782 mpdclient_get_artists(struct mpdclient *c)
784         GList *list = NULL;
785         struct mpd_pair *pair;
787         if (MPD_ERROR(c))
788                return NULL;
790         mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
791         mpd_search_commit(c->connection);
793         while ((pair = mpd_recv_pair_tag(c->connection,
794                                          MPD_TAG_ARTIST)) != NULL) {
795                 list = g_list_append(list, g_strdup(pair->value));
796                 mpd_return_pair(c->connection, pair);
797         }
799         if (mpdclient_finish_command(c))
800                 return string_list_free(list);
802         return list;
805 GList *
806 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
808         GList *list = NULL;
809         struct mpd_pair *pair;
811         if (MPD_ERROR(c))
812                return NULL;
814         mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
815         if (artist_utf8 != NULL)
816                 mpd_search_add_tag_constraint(c->connection,
817                                               MPD_OPERATOR_DEFAULT,
818                                               MPD_TAG_ARTIST, artist_utf8);
819         mpd_search_commit(c->connection);
821         while ((pair = mpd_recv_pair_tag(c->connection,
822                                          MPD_TAG_ALBUM)) != NULL) {
823                 list = g_list_append(list, g_strdup(pair->value));
824                 mpd_return_pair(c->connection, pair);
825         }
827         if (mpdclient_finish_command(c))
828                 return string_list_free(list);
830         return list;