Code

mpdclient: moved compare_filelistentry() to filelist.c
[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 /* sort by list-format */
45 gint
46 compare_filelistentry_format(gconstpointer filelist_entry1,
47                              gconstpointer filelist_entry2)
48 {
49         const struct mpd_entity *e1, *e2;
50         char key1[BUFSIZE], key2[BUFSIZE];
51         int n = 0;
53         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
54         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
56         if (e1 && e2 &&
57             mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
58             mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
59                 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
60                 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
61                 n = strcmp(key1,key2);
62         }
64         return n;
65 }
68 /****************************************************************************/
69 /*** mpdclient functions ****************************************************/
70 /****************************************************************************/
72 bool
73 mpdclient_handle_error(struct mpdclient *c)
74 {
75         enum mpd_error error = mpd_connection_get_error(c->connection);
77         assert(error != MPD_ERROR_SUCCESS);
79         if (error == MPD_ERROR_SERVER &&
80             mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
81             screen_auth(c))
82                 return true;
84         mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
86         if (!mpd_connection_clear_error(c->connection))
87                 mpdclient_disconnect(c);
89         return false;
90 }
92 static bool
93 mpdclient_finish_command(struct mpdclient *c)
94 {
95         return mpd_response_finish(c->connection)
96                 ? true : mpdclient_handle_error(c);
97 }
99 struct mpdclient *
100 mpdclient_new(void)
102         struct mpdclient *c;
104         c = g_new0(struct mpdclient, 1);
105         playlist_init(&c->playlist);
106         c->volume = -1;
107         c->events = 0;
109         return c;
112 void
113 mpdclient_free(struct mpdclient *c)
115         mpdclient_disconnect(c);
117         mpdclient_playlist_free(&c->playlist);
119         g_free(c);
122 void
123 mpdclient_disconnect(struct mpdclient *c)
125         if (c->connection)
126                 mpd_connection_free(c->connection);
127         c->connection = NULL;
129         if (c->status)
130                 mpd_status_free(c->status);
131         c->status = NULL;
133         playlist_clear(&c->playlist);
135         if (c->song)
136                 c->song = NULL;
139 bool
140 mpdclient_connect(struct mpdclient *c,
141                   const gchar *host,
142                   gint port,
143                   gfloat _timeout,
144                   const gchar *password)
146         /* close any open connection */
147         if( c->connection )
148                 mpdclient_disconnect(c);
150         /* connect to MPD */
151         c->connection = mpd_connection_new(host, port, _timeout * 1000);
152         if (c->connection == NULL)
153                 g_error("Out of memory");
155         if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
156                 mpdclient_handle_error(c);
157                 mpdclient_disconnect(c);
158                 return false;
159         }
161         /* send password */
162         if (password != NULL && !mpd_run_password(c->connection, password)) {
163                 mpdclient_handle_error(c);
164                 mpdclient_disconnect(c);
165                 return false;
166         }
168         return true;
171 bool
172 mpdclient_update(struct mpdclient *c)
174         bool retval;
176         c->volume = -1;
178         if (MPD_ERROR(c))
179                 return false;
181         /* always announce these options as long as we don't have real
182            "idle" support */
183         c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
185         /* free the old status */
186         if (c->status)
187                 mpd_status_free(c->status);
189         /* retrieve new status */
190         c->status = mpd_run_status(c->connection);
191         if (c->status == NULL)
192                 return mpdclient_handle_error(c);
194         if (c->update_id != mpd_status_get_update_id(c->status)) {
195                 c->events |= MPD_IDLE_UPDATE;
197                 if (c->update_id > 0)
198                         c->events |= MPD_IDLE_DATABASE;
199         }
201         c->update_id = mpd_status_get_update_id(c->status);
203         if (c->volume != mpd_status_get_volume(c->status))
204                 c->events |= MPD_IDLE_MIXER;
206         c->volume = mpd_status_get_volume(c->status);
208         /* check if the playlist needs an update */
209         if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
210                 c->events |= MPD_IDLE_PLAYLIST;
212                 if (!playlist_is_empty(&c->playlist))
213                         retval = mpdclient_playlist_update_changes(c);
214                 else
215                         retval = mpdclient_playlist_update(c);
216         } else
217                 retval = true;
219         /* update the current song */
220         if (!c->song || mpd_status_get_song_id(c->status)) {
221                 c->song = playlist_get_song(&c->playlist,
222                                             mpd_status_get_song_pos(c->status));
223         }
225         return retval;
229 /****************************************************************************/
230 /*** MPD Commands  **********************************************************/
231 /****************************************************************************/
233 bool
234 mpdclient_cmd_play(struct mpdclient *c, gint idx)
236         const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
238         if (MPD_ERROR(c))
239                 return false;
241         if (song)
242                 mpd_send_play_id(c->connection, mpd_song_get_id(song));
243         else
244                 mpd_send_play(c->connection);
246         return mpdclient_finish_command(c);
249 bool
250 mpdclient_cmd_crop(struct mpdclient *c)
252         struct mpd_status *status;
253         bool playing;
254         int length, current;
256         if (MPD_ERROR(c))
257                 return false;
259         status = mpd_run_status(c->connection);
260         if (status == NULL)
261                 return mpdclient_handle_error(c);
263         playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
264                 mpd_status_get_state(status) == MPD_STATE_PAUSE;
265         length = mpd_status_get_queue_length(status);
266         current = mpd_status_get_song_pos(status);
268         mpd_status_free(status);
270         if (!playing || length < 2)
271                 return true;
273         mpd_command_list_begin(c->connection, false);
275         while (--length >= 0)
276                 if (length != current)
277                         mpd_send_delete(c->connection, length);
279         mpd_command_list_end(c->connection);
281         return mpdclient_finish_command(c);
284 bool
285 mpdclient_cmd_clear(struct mpdclient *c)
287         bool retval;
289         if (MPD_ERROR(c))
290                 return false;
292         mpd_send_clear(c->connection);
293         retval = mpdclient_finish_command(c);
295         if (retval)
296                 c->events |= MPD_IDLE_PLAYLIST;
298         return retval;
301 bool
302 mpdclient_cmd_volume(struct mpdclient *c, gint value)
304         if (MPD_ERROR(c))
305                 return false;
307         mpd_send_set_volume(c->connection, value);
308         return mpdclient_finish_command(c);
311 bool
312 mpdclient_cmd_volume_up(struct mpdclient *c)
314         if (MPD_ERROR(c))
315                 return false;
317         if (c->status == NULL ||
318             mpd_status_get_volume(c->status) == -1)
319                 return true;
321         if (c->volume < 0)
322                 c->volume = mpd_status_get_volume(c->status);
324         if (c->volume >= 100)
325                 return true;
327         return mpdclient_cmd_volume(c, ++c->volume);
330 bool
331 mpdclient_cmd_volume_down(struct mpdclient *c)
333         if (MPD_ERROR(c))
334                 return false;
336         if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
337                 return true;
339         if (c->volume < 0)
340                 c->volume = mpd_status_get_volume(c->status);
342         if (c->volume <= 0)
343                 return true;
345         return mpdclient_cmd_volume(c, --c->volume);
348 bool
349 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
351         if (MPD_ERROR(c))
352                 return false;
354         mpd_send_add(c->connection, path_utf8);
355         return mpdclient_finish_command(c);
358 bool
359 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
361         struct mpd_status *status;
362         struct mpd_song *new_song;
364         assert(c != NULL);
365         assert(song != NULL);
367         if (MPD_ERROR(c) || c->status == NULL)
368                 return false;
370         /* send the add command to mpd; at the same time, get the new
371            status (to verify the new playlist id) and the last song
372            (we hope that's the song we just added) */
374         if (!mpd_command_list_begin(c->connection, true) ||
375             !mpd_send_add(c->connection, mpd_song_get_uri(song)) ||
376             !mpd_send_status(c->connection) ||
377             !mpd_send_get_queue_song_pos(c->connection,
378                                          playlist_length(&c->playlist)) ||
379             !mpd_command_list_end(c->connection) ||
380             !mpd_response_next(c->connection))
381                 return mpdclient_handle_error(c);
383         c->events |= MPD_IDLE_PLAYLIST;
385         status = mpd_recv_status(c->connection);
386         if (status != NULL) {
387                 if (c->status != NULL)
388                         mpd_status_free(c->status);
389                 c->status = status;
390         }
392         if (!mpd_response_next(c->connection))
393                 return mpdclient_handle_error(c);
395         new_song = mpd_recv_song(c->connection);
396         if (!mpd_response_finish(c->connection) || new_song == NULL) {
397                 if (new_song != NULL)
398                         mpd_song_free(new_song);
400                 return mpd_connection_clear_error(c->connection) ||
401                         mpdclient_handle_error(c);
402         }
404         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
405             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
406                 /* the cheap route: match on the new playlist length
407                    and its version, we can keep our local playlist
408                    copy in sync */
409                 c->playlist.version = mpd_status_get_queue_version(status);
411                 /* the song we just received has the correct id;
412                    append it to the local playlist */
413                 playlist_append(&c->playlist, new_song);
414         }
416         mpd_song_free(new_song);
418         return true;
421 bool
422 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
424         const struct mpd_song *song;
425         struct mpd_status *status;
427         if (MPD_ERROR(c) || c->status == NULL)
428                 return false;
430         if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
431                 return false;
433         song = playlist_get(&c->playlist, idx);
435         /* send the delete command to mpd; at the same time, get the
436            new status (to verify the playlist id) */
438         if (!mpd_command_list_begin(c->connection, false) ||
439             !mpd_send_delete_id(c->connection, mpd_song_get_id(song)) ||
440             !mpd_send_status(c->connection) ||
441             !mpd_command_list_end(c->connection))
442                 return mpdclient_handle_error(c);
444         c->events |= MPD_IDLE_PLAYLIST;
446         status = mpd_recv_status(c->connection);
447         if (status != NULL) {
448                 if (c->status != NULL)
449                         mpd_status_free(c->status);
450                 c->status = status;
451         }
453         if (!mpd_response_finish(c->connection))
454                 return mpdclient_handle_error(c);
456         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
457             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
458                 /* the cheap route: match on the new playlist length
459                    and its version, we can keep our local playlist
460                    copy in sync */
461                 c->playlist.version = mpd_status_get_queue_version(status);
463                 /* remove the song from the local playlist */
464                 playlist_remove(&c->playlist, idx);
466                 /* remove references to the song */
467                 if (c->song == song)
468                         c->song = NULL;
469         }
471         return true;
474 /**
475  * Fallback for mpdclient_cmd_delete_range() on MPD older than 0.16.
476  * It emulates the "delete range" command with a list of simple
477  * "delete" commands.
478  */
479 static bool
480 mpdclient_cmd_delete_range_fallback(struct mpdclient *c,
481                                     unsigned start, unsigned end)
483         if (!mpd_command_list_begin(c->connection, false))
484                 return mpdclient_handle_error(c);
486         for (; start < end; --end)
487                 mpd_send_delete(c->connection, start);
489         if (!mpd_command_list_end(c->connection) ||
490             !mpd_response_finish(c->connection))
491                 return mpdclient_handle_error(c);
493         return true;
496 bool
497 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
499         struct mpd_status *status;
501         if (MPD_ERROR(c))
502                 return false;
504         if (mpd_connection_cmp_server_version(c->connection, 0, 16, 0) < 0)
505                 return mpdclient_cmd_delete_range_fallback(c, start, end);
507         /* MPD 0.16 supports "delete" with a range argument */
509         /* send the delete command to mpd; at the same time, get the
510            new status (to verify the playlist id) */
512         if (!mpd_command_list_begin(c->connection, false) ||
513             !mpd_send_delete_range(c->connection, start, end) ||
514             !mpd_send_status(c->connection) ||
515             !mpd_command_list_end(c->connection))
516                 return mpdclient_handle_error(c);
518         c->events |= MPD_IDLE_PLAYLIST;
520         status = mpd_recv_status(c->connection);
521         if (status != NULL) {
522                 if (c->status != NULL)
523                         mpd_status_free(c->status);
524                 c->status = status;
525         }
527         if (!mpd_response_finish(c->connection))
528                 return mpdclient_handle_error(c);
530         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
531             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
532                 /* the cheap route: match on the new playlist length
533                    and its version, we can keep our local playlist
534                    copy in sync */
535                 c->playlist.version = mpd_status_get_queue_version(status);
537                 /* remove the song from the local playlist */
538                 while (end > start) {
539                         --end;
541                         /* remove references to the song */
542                         if (c->song == playlist_get(&c->playlist, end))
543                                 c->song = NULL;
545                         playlist_remove(&c->playlist, end);
546                 }
547         }
549         return true;
552 bool
553 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
555         const struct mpd_song *song1, *song2;
556         struct mpd_status *status;
558         if (MPD_ERROR(c))
559                 return false;
561         if (old_index == new_index || new_index < 0 ||
562             (guint)new_index >= c->playlist.list->len)
563                 return false;
565         song1 = playlist_get(&c->playlist, old_index);
566         song2 = playlist_get(&c->playlist, new_index);
568         /* send the delete command to mpd; at the same time, get the
569            new status (to verify the playlist id) */
571         if (!mpd_command_list_begin(c->connection, false) ||
572             !mpd_send_swap_id(c->connection, mpd_song_get_id(song1),
573                               mpd_song_get_id(song2)) ||
574             !mpd_send_status(c->connection) ||
575             !mpd_command_list_end(c->connection))
576                 return mpdclient_handle_error(c);
578         c->events |= MPD_IDLE_PLAYLIST;
580         status = mpd_recv_status(c->connection);
581         if (status != NULL) {
582                 if (c->status != NULL)
583                         mpd_status_free(c->status);
584                 c->status = status;
585         }
587         if (!mpd_response_finish(c->connection))
588                 return mpdclient_handle_error(c);
590         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
591             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
592                 /* the cheap route: match on the new playlist length
593                    and its version, we can keep our local playlist
594                    copy in sync */
595                 c->playlist.version = mpd_status_get_queue_version(status);
597                 /* swap songs in the local playlist */
598                 playlist_swap(&c->playlist, old_index, new_index);
599         }
601         return true;
605 /****************************************************************************/
606 /*** Playlist management functions ******************************************/
607 /****************************************************************************/
609 /* update playlist */
610 bool
611 mpdclient_playlist_update(struct mpdclient *c)
613         struct mpd_entity *entity;
615         if (MPD_ERROR(c))
616                 return false;
618         playlist_clear(&c->playlist);
620         mpd_send_list_queue_meta(c->connection);
621         while ((entity = mpd_recv_entity(c->connection))) {
622                 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
623                         playlist_append(&c->playlist, mpd_entity_get_song(entity));
625                 mpd_entity_free(entity);
626         }
628         c->playlist.version = mpd_status_get_queue_version(c->status);
629         c->song = NULL;
631         return mpdclient_finish_command(c);
634 /* update playlist (plchanges) */
635 bool
636 mpdclient_playlist_update_changes(struct mpdclient *c)
638         struct mpd_song *song;
639         guint length;
641         if (MPD_ERROR(c))
642                 return false;
644         mpd_send_queue_changes_meta(c->connection, c->playlist.version);
646         while ((song = mpd_recv_song(c->connection)) != NULL) {
647                 int pos = mpd_song_get_pos(song);
649                 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
650                         /* update song */
651                         playlist_replace(&c->playlist, pos, song);
652                 } else {
653                         /* add a new song */
654                         playlist_append(&c->playlist, song);
655                 }
657                 mpd_song_free(song);
658         }
660         /* remove trailing songs */
662         length = mpd_status_get_queue_length(c->status);
663         while (length < c->playlist.list->len) {
664                 guint pos = c->playlist.list->len - 1;
666                 /* Remove the last playlist entry */
667                 playlist_remove(&c->playlist, pos);
668         }
670         c->song = NULL;
671         c->playlist.version = mpd_status_get_queue_version(c->status);
673         return mpdclient_finish_command(c);
677 /****************************************************************************/
678 /*** Filelist functions *****************************************************/
679 /****************************************************************************/
681 static struct filelist *
682 mpdclient_recv_filelist_response(struct mpdclient *c);
684 struct filelist *
685 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
687         struct filelist *filelist;
689         if (MPD_ERROR(c))
690                 return NULL;
692         mpd_send_list_meta(c->connection, path);
693         filelist = mpdclient_recv_filelist_response(c);
694         if (filelist == NULL)
695                 return NULL;
697         filelist_sort_dir_play(filelist, compare_filelist_entry_path);
699         return filelist;
702 static struct filelist *
703 mpdclient_recv_filelist_response(struct mpdclient *c)
705         struct filelist *filelist;
707         filelist = filelist_new_recv(c->connection);
709         if (!mpdclient_finish_command(c)) {
710                 filelist_free(filelist);
711                 return NULL;
712         }
714         return filelist;
717 bool
718 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
720         guint i;
722         if (MPD_ERROR(c))
723                 return false;
725         if (filelist_is_empty(fl))
726                 return true;
728         mpd_command_list_begin(c->connection, false);
730         for (i = 0; i < filelist_length(fl); ++i) {
731                 struct filelist_entry *entry = filelist_get(fl, i);
732                 struct mpd_entity *entity  = entry->entity;
734                 if (entity != NULL &&
735                     mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
736                         const struct mpd_song *song =
737                                 mpd_entity_get_song(entity);
739                         mpd_send_add(c->connection, mpd_song_get_uri(song));
740                 }
741         }
743         mpd_command_list_end(c->connection);
744         return mpdclient_finish_command(c);
747 GList *
748 mpdclient_get_artists(struct mpdclient *c)
750         GList *list = NULL;
751         struct mpd_pair *pair;
753         if (MPD_ERROR(c))
754                return NULL;
756         mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
757         mpd_search_commit(c->connection);
759         while ((pair = mpd_recv_pair_tag(c->connection,
760                                          MPD_TAG_ARTIST)) != NULL) {
761                 list = g_list_append(list, g_strdup(pair->value));
762                 mpd_return_pair(c->connection, pair);
763         }
765         if (!mpdclient_finish_command(c))
766                 return string_list_free(list);
768         return list;
771 GList *
772 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
774         GList *list = NULL;
775         struct mpd_pair *pair;
777         if (MPD_ERROR(c))
778                return NULL;
780         mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
781         if (artist_utf8 != NULL)
782                 mpd_search_add_tag_constraint(c->connection,
783                                               MPD_OPERATOR_DEFAULT,
784                                               MPD_TAG_ARTIST, artist_utf8);
785         mpd_search_commit(c->connection);
787         while ((pair = mpd_recv_pair_tag(c->connection,
788                                          MPD_TAG_ALBUM)) != NULL) {
789                 list = g_list_append(list, g_strdup(pair->value));
790                 mpd_return_pair(c->connection, pair);
791         }
793         if (!mpdclient_finish_command(c))
794                 return string_list_free(list);
796         return list;