Code

mpdclient: rewrite the "fancy delete" 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_MOVE
37 #define BUFSIZE 1024
39 static bool
40 MPD_ERROR(const struct mpdclient *client)
41 {
42         return client->connection == NULL ||
43                 mpd_connection_get_error(client->connection) != MPD_ERROR_SUCCESS;
44 }
46 /* filelist sorting functions */
47 static gint
48 compare_filelistentry(gconstpointer filelist_entry1,
49                           gconstpointer filelist_entry2)
50 {
51         const struct mpd_entity *e1, *e2;
52         int n = 0;
54         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
55         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
57         if (e1 != NULL && e2 != NULL &&
58             mpd_entity_get_type(e1) == mpd_entity_get_type(e2)) {
59                 switch (mpd_entity_get_type(e1)) {
60                 case MPD_ENTITY_TYPE_UNKNOWN:
61                         break;
62                 case MPD_ENTITY_TYPE_DIRECTORY:
63                         n = g_utf8_collate(mpd_directory_get_path(mpd_entity_get_directory(e1)),
64                                            mpd_directory_get_path(mpd_entity_get_directory(e2)));
65                         break;
66                 case MPD_ENTITY_TYPE_SONG:
67                         break;
68                 case MPD_ENTITY_TYPE_PLAYLIST:
69                         n = g_utf8_collate(mpd_playlist_get_path(mpd_entity_get_playlist(e1)),
70                                            mpd_playlist_get_path(mpd_entity_get_playlist(e2)));
71                 }
72         }
73         return n;
74 }
76 /* sort by list-format */
77 gint
78 compare_filelistentry_format(gconstpointer filelist_entry1,
79                              gconstpointer filelist_entry2)
80 {
81         const struct mpd_entity *e1, *e2;
82         char key1[BUFSIZE], key2[BUFSIZE];
83         int n = 0;
85         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
86         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
88         if (e1 && e2 &&
89             mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
90             mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
91                 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
92                 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
93                 n = strcmp(key1,key2);
94         }
96         return n;
97 }
100 /****************************************************************************/
101 /*** mpdclient functions ****************************************************/
102 /****************************************************************************/
104 gint
105 mpdclient_handle_error(struct mpdclient *c)
107         enum mpd_error error = mpd_connection_get_error(c->connection);
109         assert(error != MPD_ERROR_SUCCESS);
111         if (error == MPD_ERROR_SERVER &&
112             mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
113             screen_auth(c))
114                 return 0;
116         if (error == MPD_ERROR_SERVER)
117                 error = error | (mpd_connection_get_server_error(c->connection) << 8);
119         mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
121         if (!mpd_connection_clear_error(c->connection))
122                 mpdclient_disconnect(c);
124         return error;
127 static gint
128 mpdclient_finish_command(struct mpdclient *c)
130         return mpd_response_finish(c->connection)
131                 ? 0 : mpdclient_handle_error(c);
134 struct mpdclient *
135 mpdclient_new(void)
137         struct mpdclient *c;
139         c = g_new0(struct mpdclient, 1);
140         playlist_init(&c->playlist);
141         c->volume = -1;
142         c->events = 0;
144         return c;
147 void
148 mpdclient_free(struct mpdclient *c)
150         mpdclient_disconnect(c);
152         mpdclient_playlist_free(&c->playlist);
154         g_free(c);
157 void
158 mpdclient_disconnect(struct mpdclient *c)
160         if (c->connection)
161                 mpd_connection_free(c->connection);
162         c->connection = NULL;
164         if (c->status)
165                 mpd_status_free(c->status);
166         c->status = NULL;
168         playlist_clear(&c->playlist);
170         if (c->song)
171                 c->song = NULL;
174 bool
175 mpdclient_connect(struct mpdclient *c,
176                   const gchar *host,
177                   gint port,
178                   gfloat _timeout,
179                   const gchar *password)
181         /* close any open connection */
182         if( c->connection )
183                 mpdclient_disconnect(c);
185         /* connect to MPD */
186         c->connection = mpd_connection_new(host, port, _timeout * 1000);
187         if (c->connection == NULL)
188                 g_error("Out of memory");
190         if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
191                 mpdclient_handle_error(c);
192                 mpdclient_disconnect(c);
193                 return false;
194         }
196         /* send password */
197         if (password != NULL && !mpd_run_password(c->connection, password)) {
198                 mpdclient_handle_error(c);
199                 mpdclient_disconnect(c);
200                 return false;
201         }
203         return true;
206 bool
207 mpdclient_update(struct mpdclient *c)
209         bool retval;
211         c->volume = -1;
213         if (MPD_ERROR(c))
214                 return false;
216         /* always announce these options as long as we don't have real
217            "idle" support */
218         c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
220         /* free the old status */
221         if (c->status)
222                 mpd_status_free(c->status);
224         /* retrieve new status */
225         c->status = mpd_run_status(c->connection);
226         if (c->status == NULL)
227                 return mpdclient_handle_error(c) == 0;
229         if (c->update_id != mpd_status_get_update_id(c->status)) {
230                 c->events |= MPD_IDLE_UPDATE;
232                 if (c->update_id > 0)
233                         c->events |= MPD_IDLE_DATABASE;
234         }
236         c->update_id = mpd_status_get_update_id(c->status);
238         if (c->volume != mpd_status_get_volume(c->status))
239                 c->events |= MPD_IDLE_MIXER;
241         c->volume = mpd_status_get_volume(c->status);
243         /* check if the playlist needs an update */
244         if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
245                 c->events |= MPD_IDLE_PLAYLIST;
247                 if (!playlist_is_empty(&c->playlist))
248                         retval = mpdclient_playlist_update_changes(c);
249                 else
250                         retval = mpdclient_playlist_update(c);
251         } else
252                 retval = true;
254         /* update the current song */
255         if (!c->song || mpd_status_get_song_id(c->status)) {
256                 c->song = playlist_get_song(&c->playlist,
257                                             mpd_status_get_song_pos(c->status));
258         }
260         return retval;
264 /****************************************************************************/
265 /*** MPD Commands  **********************************************************/
266 /****************************************************************************/
268 gint
269 mpdclient_cmd_play(struct mpdclient *c, gint idx)
271         const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
273         if (MPD_ERROR(c))
274                 return -1;
276         if (song)
277                 mpd_send_play_id(c->connection, mpd_song_get_id(song));
278         else
279                 mpd_send_play(c->connection);
281         return mpdclient_finish_command(c);
284 gint
285 mpdclient_cmd_crop(struct mpdclient *c)
287         struct mpd_status *status;
288         bool playing;
289         int length, current;
291         if (MPD_ERROR(c))
292                 return -1;
294         status = mpd_run_status(c->connection);
295         if (status == NULL)
296                 return mpdclient_handle_error(c);
298         playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
299                 mpd_status_get_state(status) == MPD_STATE_PAUSE;
300         length = mpd_status_get_queue_length(status);
301         current = mpd_status_get_song_pos(status);
303         mpd_status_free(status);
305         if (!playing || length < 2)
306                 return 0;
308         mpd_command_list_begin(c->connection, false);
310         while (--length >= 0)
311                 if (length != current)
312                         mpd_send_delete(c->connection, length);
314         mpd_command_list_end(c->connection);
316         return mpdclient_finish_command(c);
319 gint
320 mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end)
322         mpd_send_shuffle_range(c->connection, start, end);
323         return mpdclient_finish_command(c);
326 gint
327 mpdclient_cmd_clear(struct mpdclient *c)
329         gint retval = 0;
331         if (MPD_ERROR(c))
332                 return -1;
334         mpd_send_clear(c->connection);
335         retval = mpdclient_finish_command(c);
337         if (retval)
338                 c->events |= MPD_IDLE_PLAYLIST;
340         return retval;
343 gint
344 mpdclient_cmd_volume(struct mpdclient *c, gint value)
346         if (MPD_ERROR(c))
347                 return -1;
349         mpd_send_set_volume(c->connection, value);
350         return mpdclient_finish_command(c);
353 gint mpdclient_cmd_volume_up(struct mpdclient *c)
355         if (MPD_ERROR(c))
356                 return -1;
358         if (c->status == NULL ||
359             mpd_status_get_volume(c->status) == -1)
360                 return 0;
362         if (c->volume < 0)
363                 c->volume = mpd_status_get_volume(c->status);
365         if (c->volume >= 100)
366                 return 0;
368         return mpdclient_cmd_volume(c, ++c->volume);
371 gint mpdclient_cmd_volume_down(struct mpdclient *c)
373         if (MPD_ERROR(c))
374                 return -1;
376         if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
377                 return 0;
379         if (c->volume < 0)
380                 c->volume = mpd_status_get_volume(c->status);
382         if (c->volume <= 0)
383                 return 0;
385         return mpdclient_cmd_volume(c, --c->volume);
388 gint
389 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
391         if (MPD_ERROR(c))
392                 return -1;
394         mpd_send_add(c->connection, path_utf8);
395         return mpdclient_finish_command(c);
398 gint
399 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
401         struct mpd_status *status;
402         struct mpd_song *new_song;
404         assert(c != NULL);
405         assert(song != NULL);
407         if (MPD_ERROR(c) || c->status == NULL)
408                 return -1;
410         /* send the add command to mpd; at the same time, get the new
411            status (to verify the new playlist id) and the last song
412            (we hope that's the song we just added) */
414         if (!mpd_command_list_begin(c->connection, true) ||
415             !mpd_send_add(c->connection, mpd_song_get_uri(song)) ||
416             !mpd_send_status(c->connection) ||
417             !mpd_send_get_queue_song_pos(c->connection,
418                                          playlist_length(&c->playlist)) ||
419             !mpd_command_list_end(c->connection) ||
420             !mpd_response_next(c->connection))
421                 return mpdclient_handle_error(c);
423         c->events |= MPD_IDLE_PLAYLIST;
425         status = mpd_recv_status(c->connection);
426         if (status != NULL) {
427                 if (c->status != NULL)
428                         mpd_status_free(c->status);
429                 c->status = status;
430         }
432         if (!mpd_response_next(c->connection))
433                 return mpdclient_handle_error(c);
435         new_song = mpd_recv_song(c->connection);
436         if (!mpd_response_finish(c->connection) || new_song == NULL) {
437                 if (new_song != NULL)
438                         mpd_song_free(new_song);
440                 return mpd_connection_clear_error(c->connection)
441                         ? 0 : mpdclient_handle_error(c);
442         }
444         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
445             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
446                 /* the cheap route: match on the new playlist length
447                    and its version, we can keep our local playlist
448                    copy in sync */
449                 c->playlist.version = mpd_status_get_queue_version(status);
451                 /* the song we just received has the correct id;
452                    append it to the local playlist */
453                 playlist_append(&c->playlist, new_song);
454         }
456         mpd_song_free(new_song);
458         return -0;
461 gint
462 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
464         const struct mpd_song *song;
465         struct mpd_status *status;
467         if (MPD_ERROR(c) || c->status == NULL)
468                 return -1;
470         if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
471                 return -1;
473         song = playlist_get(&c->playlist, idx);
475         /* send the delete command to mpd; at the same time, get the
476            new status (to verify the playlist id) */
478         if (!mpd_command_list_begin(c->connection, false) ||
479             !mpd_send_delete_id(c->connection, mpd_song_get_id(song)) ||
480             !mpd_send_status(c->connection) ||
481             !mpd_command_list_end(c->connection))
482                 return mpdclient_handle_error(c);
484         c->events |= MPD_IDLE_PLAYLIST;
486         status = mpd_recv_status(c->connection);
487         if (status != NULL) {
488                 if (c->status != NULL)
489                         mpd_status_free(c->status);
490                 c->status = status;
491         }
493         if (!mpd_response_finish(c->connection))
494                 return mpdclient_handle_error(c);
496         if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
497             mpd_status_get_queue_version(status) == c->playlist.version + 1) {
498                 /* the cheap route: match on the new playlist length
499                    and its version, we can keep our local playlist
500                    copy in sync */
501                 c->playlist.version = mpd_status_get_queue_version(status);
503                 /* remove the song from the local playlist */
504                 playlist_remove(&c->playlist, idx);
506                 /* remove references to the song */
507                 if (c->song == song)
508                         c->song = NULL;
509         }
511         return 0;
514 gint
515 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
517         gint n;
518         struct mpd_song *song1, *song2;
520         if (MPD_ERROR(c))
521                 return -1;
523         if (old_index == new_index || new_index < 0 ||
524             (guint)new_index >= c->playlist.list->len)
525                 return -1;
527         song1 = playlist_get(&c->playlist, old_index);
528         song2 = playlist_get(&c->playlist, new_index);
530         /* send the move command to mpd */
531         mpd_send_swap_id(c->connection,
532                          mpd_song_get_id(song1), mpd_song_get_id(song2));
533         if( (n=mpdclient_finish_command(c)) )
534                 return n;
536 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
537         /* update the playlist */
538         playlist_swap(&c->playlist, old_index, new_index);
540         /* increment the playlist id, so we don't retrieve a new playlist */
541         c->playlist.version++;
542 #endif
544         c->events |= MPD_IDLE_PLAYLIST;
546         return 0;
549 gint
550 mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8)
552         gint retval = 0;
554         if (MPD_ERROR(c))
555                 return -1;
557         mpd_send_save(c->connection, filename_utf8);
558         if ((retval = mpdclient_finish_command(c)) == 0) {
559                 c->events |= MPD_IDLE_STORED_PLAYLIST;
560         }
562         return retval;
565 gint
566 mpdclient_cmd_load_playlist(struct mpdclient *c, const gchar *filename_utf8)
568         if (MPD_ERROR(c))
569                 return -1;
571         mpd_send_load(c->connection, filename_utf8);
572         return mpdclient_finish_command(c);
575 gint
576 mpdclient_cmd_delete_playlist(struct mpdclient *c, const gchar *filename_utf8)
578         gint retval = 0;
580         if (MPD_ERROR(c))
581                 return -1;
583         mpd_send_rm(c->connection, filename_utf8);
584         if ((retval = mpdclient_finish_command(c)) == 0)
585                 c->events |= MPD_IDLE_STORED_PLAYLIST;
587         return retval;
591 /****************************************************************************/
592 /*** Playlist management functions ******************************************/
593 /****************************************************************************/
595 /* update playlist */
596 bool
597 mpdclient_playlist_update(struct mpdclient *c)
599         struct mpd_entity *entity;
601         if (MPD_ERROR(c))
602                 return false;
604         playlist_clear(&c->playlist);
606         mpd_send_list_queue_meta(c->connection);
607         while ((entity = mpd_recv_entity(c->connection))) {
608                 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
609                         playlist_append(&c->playlist, mpd_entity_get_song(entity));
611                 mpd_entity_free(entity);
612         }
614         c->playlist.version = mpd_status_get_queue_version(c->status);
615         c->song = NULL;
617         return mpdclient_finish_command(c) == 0;
620 /* update playlist (plchanges) */
621 bool
622 mpdclient_playlist_update_changes(struct mpdclient *c)
624         struct mpd_song *song;
625         guint length;
627         if (MPD_ERROR(c))
628                 return false;
630         mpd_send_queue_changes_meta(c->connection, c->playlist.version);
632         while ((song = mpd_recv_song(c->connection)) != NULL) {
633                 int pos = mpd_song_get_pos(song);
635                 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
636                         /* update song */
637                         playlist_replace(&c->playlist, pos, song);
638                 } else {
639                         /* add a new song */
640                         playlist_append(&c->playlist, song);
641                 }
643                 mpd_song_free(song);
644         }
646         /* remove trailing songs */
648         length = mpd_status_get_queue_length(c->status);
649         while (length < c->playlist.list->len) {
650                 guint pos = c->playlist.list->len - 1;
652                 /* Remove the last playlist entry */
653                 playlist_remove(&c->playlist, pos);
654         }
656         c->song = NULL;
657         c->playlist.version = mpd_status_get_queue_version(c->status);
659         return mpdclient_finish_command(c) == 0;
663 /****************************************************************************/
664 /*** Filelist functions *****************************************************/
665 /****************************************************************************/
667 struct filelist *
668 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
670         struct filelist *filelist;
671         struct mpd_entity *entity;
673         if (MPD_ERROR(c))
674                 return NULL;
676         mpd_send_list_meta(c->connection, path);
677         filelist = filelist_new();
679         while ((entity = mpd_recv_entity(c->connection)) != NULL)
680                 filelist_append(filelist, entity);
682         if (mpdclient_finish_command(c)) {
683                 filelist_free(filelist);
684                 return NULL;
685         }
687         filelist_sort_dir_play(filelist, compare_filelistentry);
689         return filelist;
692 static struct filelist *
693 mpdclient_recv_filelist_response(struct mpdclient *c)
695         struct filelist *filelist;
696         struct mpd_entity *entity;
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         return filelist;
711 struct filelist *
712 mpdclient_filelist_search(struct mpdclient *c,
713                           int exact_match,
714                           enum mpd_tag_type tag,
715                           gchar *filter_utf8)
717         if (MPD_ERROR(c))
718                 return NULL;
720         mpd_search_db_songs(c->connection, exact_match);
721         mpd_search_add_tag_constraint(c->connection, MPD_OPERATOR_DEFAULT,
722                                       tag, filter_utf8);
723         mpd_search_commit(c->connection);
725         return mpdclient_recv_filelist_response(c);
728 int
729 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
731         guint i;
733         if (MPD_ERROR(c))
734                 return -1;
736         if (filelist_is_empty(fl))
737                 return 0;
739         mpd_command_list_begin(c->connection, false);
741         for (i = 0; i < filelist_length(fl); ++i) {
742                 struct filelist_entry *entry = filelist_get(fl, i);
743                 struct mpd_entity *entity  = entry->entity;
745                 if (entity != NULL &&
746                     mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
747                         const struct mpd_song *song =
748                                 mpd_entity_get_song(entity);
749                         const char *uri = mpd_song_get_uri(song);
751                         if (uri != NULL)
752                                 mpd_send_add(c->connection, uri);
753                 }
754         }
756         mpd_command_list_end(c->connection);
757         return mpdclient_finish_command(c);
760 GList *
761 mpdclient_get_artists(struct mpdclient *c)
763         GList *list = NULL;
764         struct mpd_pair *pair;
766         if (MPD_ERROR(c))
767                return NULL;
769         mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
770         mpd_search_commit(c->connection);
772         while ((pair = mpd_recv_pair_tag(c->connection,
773                                          MPD_TAG_ARTIST)) != NULL) {
774                 list = g_list_append(list, g_strdup(pair->value));
775                 mpd_return_pair(c->connection, pair);
776         }
778         if (mpdclient_finish_command(c))
779                 return string_list_free(list);
781         return list;
784 GList *
785 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
787         GList *list = NULL;
788         struct mpd_pair *pair;
790         if (MPD_ERROR(c))
791                return NULL;
793         mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
794         if (artist_utf8 != NULL)
795                 mpd_search_add_tag_constraint(c->connection,
796                                               MPD_OPERATOR_DEFAULT,
797                                               MPD_TAG_ARTIST, artist_utf8);
798         mpd_search_commit(c->connection);
800         while ((pair = mpd_recv_pair_tag(c->connection,
801                                          MPD_TAG_ALBUM)) != NULL) {
802                 list = g_list_append(list, g_strdup(pair->value));
803                 mpd_return_pair(c->connection, pair);
804         }
806         if (mpdclient_finish_command(c))
807                 return string_list_free(list);
809         return list;