Code

bbc8df8eec88e79b28442ba3c7fb5d90bb4db68e
[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 #define BUFSIZE 1024
37 static bool
38 MPD_ERROR(const struct mpdclient *client)
39 {
40         return !mpdclient_is_connected(client) ||
41                 mpd_connection_get_error(client->connection) != MPD_ERROR_SUCCESS;
42 }
44 /* filelist sorting functions */
45 static gint
46 compare_filelistentry(gconstpointer filelist_entry1,
47                           gconstpointer filelist_entry2)
48 {
49         const struct mpd_entity *e1, *e2;
50         int n = 0;
52         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
53         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
55         if (e1 != NULL && e2 != NULL &&
56             mpd_entity_get_type(e1) == mpd_entity_get_type(e2)) {
57                 switch (mpd_entity_get_type(e1)) {
58                 case MPD_ENTITY_TYPE_UNKNOWN:
59                         break;
60                 case MPD_ENTITY_TYPE_DIRECTORY:
61                         n = g_utf8_collate(mpd_directory_get_path(mpd_entity_get_directory(e1)),
62                                            mpd_directory_get_path(mpd_entity_get_directory(e2)));
63                         break;
64                 case MPD_ENTITY_TYPE_SONG:
65                         break;
66                 case MPD_ENTITY_TYPE_PLAYLIST:
67                         n = g_utf8_collate(mpd_playlist_get_path(mpd_entity_get_playlist(e1)),
68                                            mpd_playlist_get_path(mpd_entity_get_playlist(e2)));
69                 }
70         }
71         return n;
72 }
74 /* sort by list-format */
75 gint
76 compare_filelistentry_format(gconstpointer filelist_entry1,
77                              gconstpointer filelist_entry2)
78 {
79         const struct mpd_entity *e1, *e2;
80         char key1[BUFSIZE], key2[BUFSIZE];
81         int n = 0;
83         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
84         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
86         if (e1 && e2 &&
87             mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
88             mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
89                 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
90                 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
91                 n = strcmp(key1,key2);
92         }
94         return n;
95 }
98 /****************************************************************************/
99 /*** mpdclient functions ****************************************************/
100 /****************************************************************************/
102 bool
103 mpdclient_handle_error(struct mpdclient *c)
105         enum mpd_error error = mpd_connection_get_error(c->connection);
107         assert(error != MPD_ERROR_SUCCESS);
109         if (error == MPD_ERROR_SERVER &&
110             mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
111             screen_auth(c))
112                 return true;
114         mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
116         if (!mpd_connection_clear_error(c->connection))
117                 mpdclient_disconnect(c);
119         return false;
122 static bool
123 mpdclient_finish_command(struct mpdclient *c)
125         return mpd_response_finish(c->connection)
126                 ? true : mpdclient_handle_error(c);
129 struct mpdclient *
130 mpdclient_new(void)
132         struct mpdclient *c;
134         c = g_new0(struct mpdclient, 1);
135         playlist_init(&c->playlist);
136         c->volume = -1;
137         c->events = 0;
139         return c;
142 void
143 mpdclient_free(struct mpdclient *c)
145         mpdclient_disconnect(c);
147         mpdclient_playlist_free(&c->playlist);
149         g_free(c);
152 void
153 mpdclient_disconnect(struct mpdclient *c)
155         if (c->connection)
156                 mpd_connection_free(c->connection);
157         c->connection = NULL;
159         if (c->status)
160                 mpd_status_free(c->status);
161         c->status = NULL;
163         playlist_clear(&c->playlist);
165         if (c->song)
166                 c->song = NULL;
169 bool
170 mpdclient_connect(struct mpdclient *c,
171                   const gchar *host,
172                   gint port,
173                   gfloat _timeout,
174                   const gchar *password)
176         /* close any open connection */
177         if( c->connection )
178                 mpdclient_disconnect(c);
180         /* connect to MPD */
181         c->connection = mpd_connection_new(host, port, _timeout * 1000);
182         if (c->connection == NULL)
183                 g_error("Out of memory");
185         if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
186                 mpdclient_handle_error(c);
187                 mpdclient_disconnect(c);
188                 return false;
189         }
191         /* send password */
192         if (password != NULL && !mpd_run_password(c->connection, password)) {
193                 mpdclient_handle_error(c);
194                 mpdclient_disconnect(c);
195                 return false;
196         }
198         return true;
201 bool
202 mpdclient_update(struct mpdclient *c)
204         bool retval;
206         c->volume = -1;
208         if (MPD_ERROR(c))
209                 return false;
211         /* always announce these options as long as we don't have real
212            "idle" support */
213         c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
215         /* free the old status */
216         if (c->status)
217                 mpd_status_free(c->status);
219         /* retrieve new status */
220         c->status = mpd_run_status(c->connection);
221         if (c->status == NULL)
222                 return mpdclient_handle_error(c);
224         if (c->update_id != mpd_status_get_update_id(c->status)) {
225                 c->events |= MPD_IDLE_UPDATE;
227                 if (c->update_id > 0)
228                         c->events |= MPD_IDLE_DATABASE;
229         }
231         c->update_id = mpd_status_get_update_id(c->status);
233         if (c->volume != mpd_status_get_volume(c->status))
234                 c->events |= MPD_IDLE_MIXER;
236         c->volume = mpd_status_get_volume(c->status);
238         /* check if the playlist needs an update */
239         if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
240                 c->events |= MPD_IDLE_PLAYLIST;
242                 if (!playlist_is_empty(&c->playlist))
243                         retval = mpdclient_playlist_update_changes(c);
244                 else
245                         retval = mpdclient_playlist_update(c);
246         } else
247                 retval = true;
249         /* update the current song */
250         if (!c->song || mpd_status_get_song_id(c->status)) {
251                 c->song = playlist_get_song(&c->playlist,
252                                             mpd_status_get_song_pos(c->status));
253         }
255         return retval;
259 /****************************************************************************/
260 /*** MPD Commands  **********************************************************/
261 /****************************************************************************/
263 bool
264 mpdclient_cmd_play(struct mpdclient *c, gint idx)
266         const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
268         if (MPD_ERROR(c))
269                 return false;
271         if (song)
272                 mpd_send_play_id(c->connection, mpd_song_get_id(song));
273         else
274                 mpd_send_play(c->connection);
276         return mpdclient_finish_command(c);
279 bool
280 mpdclient_cmd_crop(struct mpdclient *c)
282         struct mpd_status *status;
283         bool playing;
284         int length, current;
286         if (MPD_ERROR(c))
287                 return false;
289         status = mpd_run_status(c->connection);
290         if (status == NULL)
291                 return mpdclient_handle_error(c);
293         playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
294                 mpd_status_get_state(status) == MPD_STATE_PAUSE;
295         length = mpd_status_get_queue_length(status);
296         current = mpd_status_get_song_pos(status);
298         mpd_status_free(status);
300         if (!playing || length < 2)
301                 return true;
303         mpd_command_list_begin(c->connection, false);
305         while (--length >= 0)
306                 if (length != current)
307                         mpd_send_delete(c->connection, length);
309         mpd_command_list_end(c->connection);
311         return mpdclient_finish_command(c);
314 bool
315 mpdclient_cmd_clear(struct mpdclient *c)
317         bool retval;
319         if (MPD_ERROR(c))
320                 return false;
322         mpd_send_clear(c->connection);
323         retval = mpdclient_finish_command(c);
325         if (retval)
326                 c->events |= MPD_IDLE_PLAYLIST;
328         return retval;
331 bool
332 mpdclient_cmd_volume(struct mpdclient *c, gint value)
334         if (MPD_ERROR(c))
335                 return false;
337         mpd_send_set_volume(c->connection, value);
338         return mpdclient_finish_command(c);
341 bool
342 mpdclient_cmd_volume_up(struct mpdclient *c)
344         if (MPD_ERROR(c))
345                 return false;
347         if (c->status == NULL ||
348             mpd_status_get_volume(c->status) == -1)
349                 return true;
351         if (c->volume < 0)
352                 c->volume = mpd_status_get_volume(c->status);
354         if (c->volume >= 100)
355                 return true;
357         return mpdclient_cmd_volume(c, ++c->volume);
360 bool
361 mpdclient_cmd_volume_down(struct mpdclient *c)
363         if (MPD_ERROR(c))
364                 return false;
366         if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
367                 return true;
369         if (c->volume < 0)
370                 c->volume = mpd_status_get_volume(c->status);
372         if (c->volume <= 0)
373                 return true;
375         return mpdclient_cmd_volume(c, --c->volume);
378 bool
379 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
381         if (MPD_ERROR(c))
382                 return false;
384         mpd_send_add(c->connection, path_utf8);
385         return mpdclient_finish_command(c);
388 bool
389 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
391         struct mpd_status *status;
392         struct mpd_song *new_song;
394         assert(c != NULL);
395         assert(song != NULL);
397         if (MPD_ERROR(c) || c->status == NULL)
398                 return false;
400         /* send the add command to mpd; at the same time, get the new
401            status (to verify the new playlist id) and the last song
402            (we hope that's the song we just added) */
404         if (!mpd_command_list_begin(c->connection, true) ||
405             !mpd_send_add(c->connection, mpd_song_get_uri(song)) ||
406             !mpd_send_status(c->connection) ||
407             !mpd_send_get_queue_song_pos(c->connection,
408                                          playlist_length(&c->playlist)) ||
409             !mpd_command_list_end(c->connection) ||
410             !mpd_response_next(c->connection))
411                 return mpdclient_handle_error(c);
413         c->events |= MPD_IDLE_PLAYLIST;
415         status = mpd_recv_status(c->connection);
416         if (status != NULL) {
417                 if (c->status != NULL)
418                         mpd_status_free(c->status);
419                 c->status = status;
420         }
422         if (!mpd_response_next(c->connection))
423                 return mpdclient_handle_error(c);
425         new_song = mpd_recv_song(c->connection);
426         if (!mpd_response_finish(c->connection) || new_song == NULL) {
427                 if (new_song != NULL)
428                         mpd_song_free(new_song);
430                 return mpd_connection_clear_error(c->connection) ||
431                         mpdclient_handle_error(c);
432         }
434         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
435             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
436                 /* the cheap route: match on the new playlist length
437                    and its version, we can keep our local playlist
438                    copy in sync */
439                 c->playlist.version = mpd_status_get_queue_version(status);
441                 /* the song we just received has the correct id;
442                    append it to the local playlist */
443                 playlist_append(&c->playlist, new_song);
444         }
446         mpd_song_free(new_song);
448         return true;
451 bool
452 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
454         const struct mpd_song *song;
455         struct mpd_status *status;
457         if (MPD_ERROR(c) || c->status == NULL)
458                 return false;
460         if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
461                 return false;
463         song = playlist_get(&c->playlist, idx);
465         /* send the delete command to mpd; at the same time, get the
466            new status (to verify the playlist id) */
468         if (!mpd_command_list_begin(c->connection, false) ||
469             !mpd_send_delete_id(c->connection, mpd_song_get_id(song)) ||
470             !mpd_send_status(c->connection) ||
471             !mpd_command_list_end(c->connection))
472                 return mpdclient_handle_error(c);
474         c->events |= MPD_IDLE_PLAYLIST;
476         status = mpd_recv_status(c->connection);
477         if (status != NULL) {
478                 if (c->status != NULL)
479                         mpd_status_free(c->status);
480                 c->status = status;
481         }
483         if (!mpd_response_finish(c->connection))
484                 return mpdclient_handle_error(c);
486         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
487             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
488                 /* the cheap route: match on the new playlist length
489                    and its version, we can keep our local playlist
490                    copy in sync */
491                 c->playlist.version = mpd_status_get_queue_version(status);
493                 /* remove the song from the local playlist */
494                 playlist_remove(&c->playlist, idx);
496                 /* remove references to the song */
497                 if (c->song == song)
498                         c->song = NULL;
499         }
501         return true;
504 /**
505  * Fallback for mpdclient_cmd_delete_range() on MPD older than 0.16.
506  * It emulates the "delete range" command with a list of simple
507  * "delete" commands.
508  */
509 static bool
510 mpdclient_cmd_delete_range_fallback(struct mpdclient *c,
511                                     unsigned start, unsigned end)
513         if (!mpd_command_list_begin(c->connection, false))
514                 return mpdclient_handle_error(c);
516         for (; start < end; --end)
517                 mpd_send_delete(c->connection, start);
519         if (!mpd_command_list_end(c->connection) ||
520             !mpd_response_finish(c->connection))
521                 return mpdclient_handle_error(c);
523         return true;
526 bool
527 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
529         struct mpd_status *status;
531         if (MPD_ERROR(c))
532                 return false;
534         if (mpd_connection_cmp_server_version(c->connection, 0, 16, 0) < 0)
535                 return mpdclient_cmd_delete_range_fallback(c, start, end);
537         /* MPD 0.16 supports "delete" with a range argument */
539         /* send the delete command to mpd; at the same time, get the
540            new status (to verify the playlist id) */
542         if (!mpd_command_list_begin(c->connection, false) ||
543             !mpd_send_delete_range(c->connection, start, end) ||
544             !mpd_send_status(c->connection) ||
545             !mpd_command_list_end(c->connection))
546                 return mpdclient_handle_error(c);
548         c->events |= MPD_IDLE_PLAYLIST;
550         status = mpd_recv_status(c->connection);
551         if (status != NULL) {
552                 if (c->status != NULL)
553                         mpd_status_free(c->status);
554                 c->status = status;
555         }
557         if (!mpd_response_finish(c->connection))
558                 return mpdclient_handle_error(c);
560         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
561             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
562                 /* the cheap route: match on the new playlist length
563                    and its version, we can keep our local playlist
564                    copy in sync */
565                 c->playlist.version = mpd_status_get_queue_version(status);
567                 /* remove the song from the local playlist */
568                 while (end > start) {
569                         --end;
571                         /* remove references to the song */
572                         if (c->song == playlist_get(&c->playlist, end))
573                                 c->song = NULL;
575                         playlist_remove(&c->playlist, end);
576                 }
577         }
579         return true;
582 bool
583 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
585         const struct mpd_song *song1, *song2;
586         struct mpd_status *status;
588         if (MPD_ERROR(c))
589                 return false;
591         if (old_index == new_index || new_index < 0 ||
592             (guint)new_index >= c->playlist.list->len)
593                 return false;
595         song1 = playlist_get(&c->playlist, old_index);
596         song2 = playlist_get(&c->playlist, new_index);
598         /* send the delete command to mpd; at the same time, get the
599            new status (to verify the playlist id) */
601         if (!mpd_command_list_begin(c->connection, false) ||
602             !mpd_send_swap_id(c->connection, mpd_song_get_id(song1),
603                               mpd_song_get_id(song2)) ||
604             !mpd_send_status(c->connection) ||
605             !mpd_command_list_end(c->connection))
606                 return mpdclient_handle_error(c);
608         c->events |= MPD_IDLE_PLAYLIST;
610         status = mpd_recv_status(c->connection);
611         if (status != NULL) {
612                 if (c->status != NULL)
613                         mpd_status_free(c->status);
614                 c->status = status;
615         }
617         if (!mpd_response_finish(c->connection))
618                 return mpdclient_handle_error(c);
620         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
621             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
622                 /* the cheap route: match on the new playlist length
623                    and its version, we can keep our local playlist
624                    copy in sync */
625                 c->playlist.version = mpd_status_get_queue_version(status);
627                 /* swap songs in the local playlist */
628                 playlist_swap(&c->playlist, old_index, new_index);
629         }
631         return true;
635 /****************************************************************************/
636 /*** Playlist management functions ******************************************/
637 /****************************************************************************/
639 /* update playlist */
640 bool
641 mpdclient_playlist_update(struct mpdclient *c)
643         struct mpd_entity *entity;
645         if (MPD_ERROR(c))
646                 return false;
648         playlist_clear(&c->playlist);
650         mpd_send_list_queue_meta(c->connection);
651         while ((entity = mpd_recv_entity(c->connection))) {
652                 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
653                         playlist_append(&c->playlist, mpd_entity_get_song(entity));
655                 mpd_entity_free(entity);
656         }
658         c->playlist.version = mpd_status_get_queue_version(c->status);
659         c->song = NULL;
661         return mpdclient_finish_command(c);
664 /* update playlist (plchanges) */
665 bool
666 mpdclient_playlist_update_changes(struct mpdclient *c)
668         struct mpd_song *song;
669         guint length;
671         if (MPD_ERROR(c))
672                 return false;
674         mpd_send_queue_changes_meta(c->connection, c->playlist.version);
676         while ((song = mpd_recv_song(c->connection)) != NULL) {
677                 int pos = mpd_song_get_pos(song);
679                 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
680                         /* update song */
681                         playlist_replace(&c->playlist, pos, song);
682                 } else {
683                         /* add a new song */
684                         playlist_append(&c->playlist, song);
685                 }
687                 mpd_song_free(song);
688         }
690         /* remove trailing songs */
692         length = mpd_status_get_queue_length(c->status);
693         while (length < c->playlist.list->len) {
694                 guint pos = c->playlist.list->len - 1;
696                 /* Remove the last playlist entry */
697                 playlist_remove(&c->playlist, pos);
698         }
700         c->song = NULL;
701         c->playlist.version = mpd_status_get_queue_version(c->status);
703         return mpdclient_finish_command(c);
707 /****************************************************************************/
708 /*** Filelist functions *****************************************************/
709 /****************************************************************************/
711 static struct filelist *
712 mpdclient_recv_filelist_response(struct mpdclient *c);
714 struct filelist *
715 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
717         struct filelist *filelist;
719         if (MPD_ERROR(c))
720                 return NULL;
722         mpd_send_list_meta(c->connection, path);
723         filelist = mpdclient_recv_filelist_response(c);
724         if (filelist == NULL)
725                 return NULL;
727         filelist_sort_dir_play(filelist, compare_filelistentry);
729         return filelist;
732 static struct filelist *
733 mpdclient_recv_filelist_response(struct mpdclient *c)
735         struct filelist *filelist;
737         filelist = filelist_new_recv(c->connection);
739         if (!mpdclient_finish_command(c)) {
740                 filelist_free(filelist);
741                 return NULL;
742         }
744         return filelist;
747 struct filelist *
748 mpdclient_filelist_search(struct mpdclient *c,
749                           int exact_match,
750                           enum mpd_tag_type tag,
751                           gchar *filter_utf8)
753         if (MPD_ERROR(c))
754                 return NULL;
756         mpd_search_db_songs(c->connection, exact_match);
757         mpd_search_add_tag_constraint(c->connection, MPD_OPERATOR_DEFAULT,
758                                       tag, filter_utf8);
759         mpd_search_commit(c->connection);
761         return mpdclient_recv_filelist_response(c);
764 bool
765 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
767         guint i;
769         if (MPD_ERROR(c))
770                 return false;
772         if (filelist_is_empty(fl))
773                 return true;
775         mpd_command_list_begin(c->connection, false);
777         for (i = 0; i < filelist_length(fl); ++i) {
778                 struct filelist_entry *entry = filelist_get(fl, i);
779                 struct mpd_entity *entity  = entry->entity;
781                 if (entity != NULL &&
782                     mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
783                         const struct mpd_song *song =
784                                 mpd_entity_get_song(entity);
786                         mpd_send_add(c->connection, mpd_song_get_uri(song));
787                 }
788         }
790         mpd_command_list_end(c->connection);
791         return mpdclient_finish_command(c);
794 GList *
795 mpdclient_get_artists(struct mpdclient *c)
797         GList *list = NULL;
798         struct mpd_pair *pair;
800         if (MPD_ERROR(c))
801                return NULL;
803         mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
804         mpd_search_commit(c->connection);
806         while ((pair = mpd_recv_pair_tag(c->connection,
807                                          MPD_TAG_ARTIST)) != NULL) {
808                 list = g_list_append(list, g_strdup(pair->value));
809                 mpd_return_pair(c->connection, pair);
810         }
812         if (!mpdclient_finish_command(c))
813                 return string_list_free(list);
815         return list;
818 GList *
819 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
821         GList *list = NULL;
822         struct mpd_pair *pair;
824         if (MPD_ERROR(c))
825                return NULL;
827         mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
828         if (artist_utf8 != NULL)
829                 mpd_search_add_tag_constraint(c->connection,
830                                               MPD_OPERATOR_DEFAULT,
831                                               MPD_TAG_ARTIST, artist_utf8);
832         mpd_search_commit(c->connection);
834         while ((pair = mpd_recv_pair_tag(c->connection,
835                                          MPD_TAG_ALBUM)) != NULL) {
836                 list = g_list_append(list, g_strdup(pair->value));
837                 mpd_return_pair(c->connection, pair);
838         }
840         if (!mpdclient_finish_command(c))
841                 return string_list_free(list);
843         return list;