Code

mpdclient: rewrite the "fancy add" code
[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 ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
36 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
38 #define BUFSIZE 1024
40 static bool
41 MPD_ERROR(const struct mpdclient *client)
42 {
43         return client->connection == NULL ||
44                 mpd_connection_get_error(client->connection) != MPD_ERROR_SUCCESS;
45 }
47 /* filelist sorting functions */
48 static gint
49 compare_filelistentry(gconstpointer filelist_entry1,
50                           gconstpointer filelist_entry2)
51 {
52         const struct mpd_entity *e1, *e2;
53         int n = 0;
55         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
56         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
58         if (e1 != NULL && e2 != NULL &&
59             mpd_entity_get_type(e1) == mpd_entity_get_type(e2)) {
60                 switch (mpd_entity_get_type(e1)) {
61                 case MPD_ENTITY_TYPE_UNKNOWN:
62                         break;
63                 case MPD_ENTITY_TYPE_DIRECTORY:
64                         n = g_utf8_collate(mpd_directory_get_path(mpd_entity_get_directory(e1)),
65                                            mpd_directory_get_path(mpd_entity_get_directory(e2)));
66                         break;
67                 case MPD_ENTITY_TYPE_SONG:
68                         break;
69                 case MPD_ENTITY_TYPE_PLAYLIST:
70                         n = g_utf8_collate(mpd_playlist_get_path(mpd_entity_get_playlist(e1)),
71                                            mpd_playlist_get_path(mpd_entity_get_playlist(e2)));
72                 }
73         }
74         return n;
75 }
77 /* sort by list-format */
78 gint
79 compare_filelistentry_format(gconstpointer filelist_entry1,
80                              gconstpointer filelist_entry2)
81 {
82         const struct mpd_entity *e1, *e2;
83         char key1[BUFSIZE], key2[BUFSIZE];
84         int n = 0;
86         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
87         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
89         if (e1 && e2 &&
90             mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
91             mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
92                 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
93                 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
94                 n = strcmp(key1,key2);
95         }
97         return n;
98 }
101 /****************************************************************************/
102 /*** mpdclient functions ****************************************************/
103 /****************************************************************************/
105 gint
106 mpdclient_handle_error(struct mpdclient *c)
108         enum mpd_error error = mpd_connection_get_error(c->connection);
110         assert(error != MPD_ERROR_SUCCESS);
112         if (error == MPD_ERROR_SERVER &&
113             mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
114             screen_auth(c))
115                 return 0;
117         if (error == MPD_ERROR_SERVER)
118                 error = error | (mpd_connection_get_server_error(c->connection) << 8);
120         mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
122         if (!mpd_connection_clear_error(c->connection))
123                 mpdclient_disconnect(c);
125         return error;
128 static gint
129 mpdclient_finish_command(struct mpdclient *c)
131         return mpd_response_finish(c->connection)
132                 ? 0 : mpdclient_handle_error(c);
135 struct mpdclient *
136 mpdclient_new(void)
138         struct mpdclient *c;
140         c = g_new0(struct mpdclient, 1);
141         playlist_init(&c->playlist);
142         c->volume = -1;
143         c->events = 0;
145         return c;
148 void
149 mpdclient_free(struct mpdclient *c)
151         mpdclient_disconnect(c);
153         mpdclient_playlist_free(&c->playlist);
155         g_free(c);
158 void
159 mpdclient_disconnect(struct mpdclient *c)
161         if (c->connection)
162                 mpd_connection_free(c->connection);
163         c->connection = NULL;
165         if (c->status)
166                 mpd_status_free(c->status);
167         c->status = NULL;
169         playlist_clear(&c->playlist);
171         if (c->song)
172                 c->song = NULL;
175 bool
176 mpdclient_connect(struct mpdclient *c,
177                   const gchar *host,
178                   gint port,
179                   gfloat _timeout,
180                   const gchar *password)
182         /* close any open connection */
183         if( c->connection )
184                 mpdclient_disconnect(c);
186         /* connect to MPD */
187         c->connection = mpd_connection_new(host, port, _timeout * 1000);
188         if (c->connection == NULL)
189                 g_error("Out of memory");
191         if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
192                 mpdclient_handle_error(c);
193                 mpdclient_disconnect(c);
194                 return false;
195         }
197         /* send password */
198         if (password != NULL && !mpd_run_password(c->connection, password)) {
199                 mpdclient_handle_error(c);
200                 mpdclient_disconnect(c);
201                 return false;
202         }
204         return true;
207 bool
208 mpdclient_update(struct mpdclient *c)
210         bool retval;
212         c->volume = -1;
214         if (MPD_ERROR(c))
215                 return false;
217         /* always announce these options as long as we don't have real
218            "idle" support */
219         c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
221         /* free the old status */
222         if (c->status)
223                 mpd_status_free(c->status);
225         /* retrieve new status */
226         c->status = mpd_run_status(c->connection);
227         if (c->status == NULL)
228                 return mpdclient_handle_error(c) == 0;
230         if (c->update_id != mpd_status_get_update_id(c->status)) {
231                 c->events |= MPD_IDLE_UPDATE;
233                 if (c->update_id > 0)
234                         c->events |= MPD_IDLE_DATABASE;
235         }
237         c->update_id = mpd_status_get_update_id(c->status);
239         if (c->volume != mpd_status_get_volume(c->status))
240                 c->events |= MPD_IDLE_MIXER;
242         c->volume = mpd_status_get_volume(c->status);
244         /* check if the playlist needs an update */
245         if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
246                 c->events |= MPD_IDLE_PLAYLIST;
248                 if (!playlist_is_empty(&c->playlist))
249                         retval = mpdclient_playlist_update_changes(c);
250                 else
251                         retval = mpdclient_playlist_update(c);
252         } else
253                 retval = true;
255         /* update the current song */
256         if (!c->song || mpd_status_get_song_id(c->status)) {
257                 c->song = playlist_get_song(&c->playlist,
258                                             mpd_status_get_song_pos(c->status));
259         }
261         return retval;
265 /****************************************************************************/
266 /*** MPD Commands  **********************************************************/
267 /****************************************************************************/
269 gint
270 mpdclient_cmd_play(struct mpdclient *c, gint idx)
272         const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
274         if (MPD_ERROR(c))
275                 return -1;
277         if (song)
278                 mpd_send_play_id(c->connection, mpd_song_get_id(song));
279         else
280                 mpd_send_play(c->connection);
282         return mpdclient_finish_command(c);
285 gint
286 mpdclient_cmd_crop(struct mpdclient *c)
288         struct mpd_status *status;
289         bool playing;
290         int length, current;
292         if (MPD_ERROR(c))
293                 return -1;
295         status = mpd_run_status(c->connection);
296         if (status == NULL)
297                 return mpdclient_handle_error(c);
299         playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
300                 mpd_status_get_state(status) == MPD_STATE_PAUSE;
301         length = mpd_status_get_queue_length(status);
302         current = mpd_status_get_song_pos(status);
304         mpd_status_free(status);
306         if (!playing || length < 2)
307                 return 0;
309         mpd_command_list_begin(c->connection, false);
311         while (--length >= 0)
312                 if (length != current)
313                         mpd_send_delete(c->connection, length);
315         mpd_command_list_end(c->connection);
317         return mpdclient_finish_command(c);
320 gint
321 mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end)
323         mpd_send_shuffle_range(c->connection, start, end);
324         return mpdclient_finish_command(c);
327 gint
328 mpdclient_cmd_clear(struct mpdclient *c)
330         gint retval = 0;
332         if (MPD_ERROR(c))
333                 return -1;
335         mpd_send_clear(c->connection);
336         retval = mpdclient_finish_command(c);
338         if (retval)
339                 c->events |= MPD_IDLE_PLAYLIST;
341         return retval;
344 gint
345 mpdclient_cmd_volume(struct mpdclient *c, gint value)
347         if (MPD_ERROR(c))
348                 return -1;
350         mpd_send_set_volume(c->connection, value);
351         return mpdclient_finish_command(c);
354 gint mpdclient_cmd_volume_up(struct mpdclient *c)
356         if (MPD_ERROR(c))
357                 return -1;
359         if (c->status == NULL ||
360             mpd_status_get_volume(c->status) == -1)
361                 return 0;
363         if (c->volume < 0)
364                 c->volume = mpd_status_get_volume(c->status);
366         if (c->volume >= 100)
367                 return 0;
369         return mpdclient_cmd_volume(c, ++c->volume);
372 gint mpdclient_cmd_volume_down(struct mpdclient *c)
374         if (MPD_ERROR(c))
375                 return -1;
377         if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
378                 return 0;
380         if (c->volume < 0)
381                 c->volume = mpd_status_get_volume(c->status);
383         if (c->volume <= 0)
384                 return 0;
386         return mpdclient_cmd_volume(c, --c->volume);
389 gint
390 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
392         if (MPD_ERROR(c))
393                 return -1;
395         mpd_send_add(c->connection, path_utf8);
396         return mpdclient_finish_command(c);
399 gint
400 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
402         struct mpd_status *status;
403         struct mpd_song *new_song;
405         assert(c != NULL);
406         assert(song != NULL);
408         if (MPD_ERROR(c) || c->status == NULL)
409                 return -1;
411         /* send the add command to mpd; at the same time, get the new
412            status (to verify the new playlist id) and the last song
413            (we hope that's the song we just added) */
415         if (!mpd_command_list_begin(c->connection, true) ||
416             !mpd_send_add(c->connection, mpd_song_get_uri(song)) ||
417             !mpd_send_status(c->connection) ||
418             !mpd_send_get_queue_song_pos(c->connection,
419                                          playlist_length(&c->playlist)) ||
420             !mpd_command_list_end(c->connection) ||
421             !mpd_response_next(c->connection))
422                 return mpdclient_handle_error(c);
424         c->events |= MPD_IDLE_PLAYLIST;
426         status = mpd_recv_status(c->connection);
427         if (status != NULL) {
428                 if (c->status != NULL)
429                         mpd_status_free(c->status);
430                 c->status = status;
431         }
433         if (!mpd_response_next(c->connection))
434                 return mpdclient_handle_error(c);
436         new_song = mpd_recv_song(c->connection);
437         if (!mpd_response_finish(c->connection) || new_song == NULL) {
438                 if (new_song != NULL)
439                         mpd_song_free(new_song);
441                 return mpd_connection_clear_error(c->connection)
442                         ? 0 : mpdclient_handle_error(c);
443         }
445         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
446             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
447                 /* the cheap route: match on the new playlist length
448                    and its version, we can keep our local playlist
449                    copy in sync */
450                 c->playlist.version = mpd_status_get_queue_version(status);
452                 /* the song we just received has the correct id;
453                    append it to the local playlist */
454                 playlist_append(&c->playlist, new_song);
455         }
457         mpd_song_free(new_song);
459         return -0;
462 gint
463 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
465         gint retval = 0;
466         struct mpd_song *song;
468         if (MPD_ERROR(c))
469                 return -1;
471         if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
472                 return -1;
474         song = playlist_get(&c->playlist, idx);
476         /* send the delete command to mpd */
477         mpd_send_delete_id(c->connection, mpd_song_get_id(song));
478         if( (retval=mpdclient_finish_command(c)) )
479                 return retval;
481 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
482         /* increment the playlist id, so we don't retrieve a new playlist */
483         c->playlist.version++;
485         /* remove the song from the playlist */
486         playlist_remove_reuse(&c->playlist, idx);
488         c->events |= MPD_IDLE_PLAYLIST;
490         /* remove references to the song */
491         if (c->song == song)
492                 c->song = NULL;
494         mpd_song_free(song);
495 #endif
497         return 0;
500 gint
501 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
503         gint n;
504         struct mpd_song *song1, *song2;
506         if (MPD_ERROR(c))
507                 return -1;
509         if (old_index == new_index || new_index < 0 ||
510             (guint)new_index >= c->playlist.list->len)
511                 return -1;
513         song1 = playlist_get(&c->playlist, old_index);
514         song2 = playlist_get(&c->playlist, new_index);
516         /* send the move command to mpd */
517         mpd_send_swap_id(c->connection,
518                          mpd_song_get_id(song1), mpd_song_get_id(song2));
519         if( (n=mpdclient_finish_command(c)) )
520                 return n;
522 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
523         /* update the playlist */
524         playlist_swap(&c->playlist, old_index, new_index);
526         /* increment the playlist id, so we don't retrieve a new playlist */
527         c->playlist.version++;
528 #endif
530         c->events |= MPD_IDLE_PLAYLIST;
532         return 0;
535 gint
536 mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8)
538         gint retval = 0;
540         if (MPD_ERROR(c))
541                 return -1;
543         mpd_send_save(c->connection, filename_utf8);
544         if ((retval = mpdclient_finish_command(c)) == 0) {
545                 c->events |= MPD_IDLE_STORED_PLAYLIST;
546         }
548         return retval;
551 gint
552 mpdclient_cmd_load_playlist(struct mpdclient *c, const gchar *filename_utf8)
554         if (MPD_ERROR(c))
555                 return -1;
557         mpd_send_load(c->connection, filename_utf8);
558         return mpdclient_finish_command(c);
561 gint
562 mpdclient_cmd_delete_playlist(struct mpdclient *c, const gchar *filename_utf8)
564         gint retval = 0;
566         if (MPD_ERROR(c))
567                 return -1;
569         mpd_send_rm(c->connection, filename_utf8);
570         if ((retval = mpdclient_finish_command(c)) == 0)
571                 c->events |= MPD_IDLE_STORED_PLAYLIST;
573         return retval;
577 /****************************************************************************/
578 /*** Playlist management functions ******************************************/
579 /****************************************************************************/
581 /* update playlist */
582 bool
583 mpdclient_playlist_update(struct mpdclient *c)
585         struct mpd_entity *entity;
587         if (MPD_ERROR(c))
588                 return false;
590         playlist_clear(&c->playlist);
592         mpd_send_list_queue_meta(c->connection);
593         while ((entity = mpd_recv_entity(c->connection))) {
594                 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
595                         playlist_append(&c->playlist, mpd_entity_get_song(entity));
597                 mpd_entity_free(entity);
598         }
600         c->playlist.version = mpd_status_get_queue_version(c->status);
601         c->song = NULL;
603         return mpdclient_finish_command(c) == 0;
606 /* update playlist (plchanges) */
607 bool
608 mpdclient_playlist_update_changes(struct mpdclient *c)
610         struct mpd_song *song;
611         guint length;
613         if (MPD_ERROR(c))
614                 return false;
616         mpd_send_queue_changes_meta(c->connection, c->playlist.version);
618         while ((song = mpd_recv_song(c->connection)) != NULL) {
619                 int pos = mpd_song_get_pos(song);
621                 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
622                         /* update song */
623                         playlist_replace(&c->playlist, pos, song);
624                 } else {
625                         /* add a new song */
626                         playlist_append(&c->playlist, song);
627                 }
629                 mpd_song_free(song);
630         }
632         /* remove trailing songs */
634         length = mpd_status_get_queue_length(c->status);
635         while (length < c->playlist.list->len) {
636                 guint pos = c->playlist.list->len - 1;
638                 /* Remove the last playlist entry */
639                 playlist_remove(&c->playlist, pos);
640         }
642         c->song = NULL;
643         c->playlist.version = mpd_status_get_queue_version(c->status);
645         return mpdclient_finish_command(c) == 0;
649 /****************************************************************************/
650 /*** Filelist functions *****************************************************/
651 /****************************************************************************/
653 struct filelist *
654 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
656         struct filelist *filelist;
657         struct mpd_entity *entity;
659         if (MPD_ERROR(c))
660                 return NULL;
662         mpd_send_list_meta(c->connection, path);
663         filelist = filelist_new();
665         while ((entity = mpd_recv_entity(c->connection)) != NULL)
666                 filelist_append(filelist, entity);
668         if (mpdclient_finish_command(c)) {
669                 filelist_free(filelist);
670                 return NULL;
671         }
673         filelist_sort_dir_play(filelist, compare_filelistentry);
675         return filelist;
678 static struct filelist *
679 mpdclient_recv_filelist_response(struct mpdclient *c)
681         struct filelist *filelist;
682         struct mpd_entity *entity;
684         filelist = filelist_new();
686         while ((entity = mpd_recv_entity(c->connection)) != NULL)
687                 filelist_append(filelist, entity);
689         if (mpdclient_finish_command(c)) {
690                 filelist_free(filelist);
691                 return NULL;
692         }
694         return filelist;
697 struct filelist *
698 mpdclient_filelist_search(struct mpdclient *c,
699                           int exact_match,
700                           enum mpd_tag_type tag,
701                           gchar *filter_utf8)
703         if (MPD_ERROR(c))
704                 return NULL;
706         mpd_search_db_songs(c->connection, exact_match);
707         mpd_search_add_tag_constraint(c->connection, MPD_OPERATOR_DEFAULT,
708                                       tag, filter_utf8);
709         mpd_search_commit(c->connection);
711         return mpdclient_recv_filelist_response(c);
714 int
715 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
717         guint i;
719         if (MPD_ERROR(c))
720                 return -1;
722         if (filelist_is_empty(fl))
723                 return 0;
725         mpd_command_list_begin(c->connection, false);
727         for (i = 0; i < filelist_length(fl); ++i) {
728                 struct filelist_entry *entry = filelist_get(fl, i);
729                 struct mpd_entity *entity  = entry->entity;
731                 if (entity != NULL &&
732                     mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
733                         const struct mpd_song *song =
734                                 mpd_entity_get_song(entity);
735                         const char *uri = mpd_song_get_uri(song);
737                         if (uri != NULL)
738                                 mpd_send_add(c->connection, uri);
739                 }
740         }
742         mpd_command_list_end(c->connection);
743         return mpdclient_finish_command(c);
746 GList *
747 mpdclient_get_artists(struct mpdclient *c)
749         GList *list = NULL;
750         struct mpd_pair *pair;
752         if (MPD_ERROR(c))
753                return NULL;
755         mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
756         mpd_search_commit(c->connection);
758         while ((pair = mpd_recv_pair_tag(c->connection,
759                                          MPD_TAG_ARTIST)) != NULL) {
760                 list = g_list_append(list, g_strdup(pair->value));
761                 mpd_return_pair(c->connection, pair);
762         }
764         if (mpdclient_finish_command(c))
765                 return string_list_free(list);
767         return list;
770 GList *
771 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
773         GList *list = NULL;
774         struct mpd_pair *pair;
776         if (MPD_ERROR(c))
777                return NULL;
779         mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
780         if (artist_utf8 != NULL)
781                 mpd_search_add_tag_constraint(c->connection,
782                                               MPD_OPERATOR_DEFAULT,
783                                               MPD_TAG_ARTIST, artist_utf8);
784         mpd_search_commit(c->connection);
786         while ((pair = mpd_recv_pair_tag(c->connection,
787                                          MPD_TAG_ALBUM)) != NULL) {
788                 list = g_list_append(list, g_strdup(pair->value));
789                 mpd_return_pair(c->connection, pair);
790         }
792         if (mpdclient_finish_command(c))
793                 return string_list_free(list);
795         return list;