Code

mpdclient: renamed "updatingdb" to "update_id"
[ncmpc.git] / src / mpdclient.c
1 /* ncmpc (Ncurses MPD Client)
2  * (c) 2004-2009 The Music Player Daemon Project
3  * Project homepage: http://musicpd.org
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
20 #include "mpdclient.h"
21 #include "filelist.h"
22 #include "screen_client.h"
23 #include "config.h"
24 #include "options.h"
25 #include "strfsong.h"
26 #include "utils.h"
28 #include <mpd/client.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <string.h>
35 #undef  ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD /* broken with song id's */
36 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
37 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
39 #define BUFSIZE 1024
41 static bool
42 MPD_ERROR(const struct mpdclient *client)
43 {
44         return client->connection == NULL ||
45                 mpd_connection_get_error(client->connection) != MPD_ERROR_SUCCESS;
46 }
48 /* filelist sorting functions */
49 static gint
50 compare_filelistentry(gconstpointer filelist_entry1,
51                           gconstpointer filelist_entry2)
52 {
53         const struct mpd_entity *e1, *e2;
54         int n = 0;
56         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
57         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
59         if (e1 != NULL && e2 != NULL &&
60             mpd_entity_get_type(e1) == mpd_entity_get_type(e2)) {
61                 switch (mpd_entity_get_type(e1)) {
62                 case MPD_ENTITY_TYPE_UNKNOWN:
63                         break;
64                 case MPD_ENTITY_TYPE_DIRECTORY:
65                         n = g_utf8_collate(mpd_directory_get_path(mpd_entity_get_directory(e1)),
66                                            mpd_directory_get_path(mpd_entity_get_directory(e2)));
67                         break;
68                 case MPD_ENTITY_TYPE_SONG:
69                         break;
70                 case MPD_ENTITY_TYPE_PLAYLIST:
71                         n = g_utf8_collate(mpd_playlist_get_path(mpd_entity_get_playlist(e1)),
72                                            mpd_playlist_get_path(mpd_entity_get_playlist(e2)));
73                 }
74         }
75         return n;
76 }
78 /* sort by list-format */
79 gint
80 compare_filelistentry_format(gconstpointer filelist_entry1,
81                              gconstpointer filelist_entry2)
82 {
83         const struct mpd_entity *e1, *e2;
84         char key1[BUFSIZE], key2[BUFSIZE];
85         int n = 0;
87         e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
88         e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
90         if (e1 && e2 &&
91             mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
92             mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
93                 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
94                 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
95                 n = strcmp(key1,key2);
96         }
98         return n;
99 }
102 /****************************************************************************/
103 /*** mpdclient functions ****************************************************/
104 /****************************************************************************/
106 gint
107 mpdclient_handle_error(struct mpdclient *c)
109         enum mpd_error error = mpd_connection_get_error(c->connection);
111         assert(error != MPD_ERROR_SUCCESS);
113         if (error == MPD_ERROR_SERVER &&
114             mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
115             screen_auth(c))
116                 return 0;
118         if (error == MPD_ERROR_SERVER)
119                 error = error | (mpd_connection_get_server_error(c->connection) << 8);
121         for (GList *list = c->error_callbacks; list != NULL;
122              list = list->next) {
123                 mpdc_error_cb_t cb = list->data;
124                 cb(c, error, mpd_connection_get_error_message(c->connection));
125         }
127         if (!mpd_connection_clear_error(c->connection))
128                 mpdclient_disconnect(c);
130         return error;
133 static gint
134 mpdclient_finish_command(struct mpdclient *c)
136         return mpd_response_finish(c->connection)
137                 ? 0 : mpdclient_handle_error(c);
140 struct mpdclient *
141 mpdclient_new(void)
143         struct mpdclient *c;
145         c = g_new0(struct mpdclient, 1);
146         playlist_init(&c->playlist);
147         c->volume = -1;
149         return c;
152 void
153 mpdclient_free(struct mpdclient *c)
155         mpdclient_disconnect(c);
157         mpdclient_playlist_free(&c->playlist);
159         g_list_free(c->error_callbacks);
160         g_list_free(c->playlist_callbacks);
161         g_list_free(c->browse_callbacks);
162         g_free(c);
165 void
166 mpdclient_disconnect(struct mpdclient *c)
168         if (c->connection)
169                 mpd_connection_free(c->connection);
170         c->connection = NULL;
172         if (c->status)
173                 mpd_status_free(c->status);
174         c->status = NULL;
176         playlist_clear(&c->playlist);
178         if (c->song)
179                 c->song = NULL;
182 bool
183 mpdclient_connect(struct mpdclient *c,
184                   const gchar *host,
185                   gint port,
186                   gfloat _timeout,
187                   const gchar *password)
189         /* close any open connection */
190         if( c->connection )
191                 mpdclient_disconnect(c);
193         /* connect to MPD */
194         c->connection = mpd_connection_new(host, port, _timeout * 1000);
195         if (c->connection == NULL)
196                 g_error("Out of memory");
198         if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
199                 mpdclient_handle_error(c);
200                 mpdclient_disconnect(c);
201                 return false;
202         }
204         /* send password */
205         if (password != NULL && !mpd_run_password(c->connection, password)) {
206                 mpdclient_handle_error(c);
207                 mpdclient_disconnect(c);
208                 return false;
209         }
211         return true;
214 bool
215 mpdclient_update(struct mpdclient *c)
217         bool retval;
219         c->volume = -1;
221         if (MPD_ERROR(c))
222                 return false;
224         /* free the old status */
225         if (c->status)
226                 mpd_status_free(c->status);
228         /* retrieve new status */
229         c->status = mpd_run_status(c->connection);
230         if (c->status == NULL)
231                 return mpdclient_handle_error(c) == 0;
233         if (c->update_id > 0 &&
234             c->update_id != mpd_status_get_update_id(c->status))
235                 mpdclient_browse_callback(c, BROWSE_DB_UPDATED, NULL);
237         c->update_id = mpd_status_get_update_id(c->status);
238         c->volume = mpd_status_get_volume(c->status);
240         /* check if the playlist needs an update */
241         if (c->playlist.id != mpd_status_get_queue_version(c->status)) {
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 gint
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 -1;
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 gint
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 -1;
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 0;
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 gint
315 mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end)
317         mpd_send_shuffle_range(c->connection, start, end);
318         return mpdclient_finish_command(c);
321 gint
322 mpdclient_cmd_clear(struct mpdclient *c)
324         gint retval = 0;
326         if (MPD_ERROR(c))
327                 return -1;
329         mpd_send_clear(c->connection);
330         retval = mpdclient_finish_command(c);
331         /* call playlist updated callback */
332         mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL);
333         return retval;
336 gint
337 mpdclient_cmd_volume(struct mpdclient *c, gint value)
339         if (MPD_ERROR(c))
340                 return -1;
342         mpd_send_set_volume(c->connection, value);
343         return mpdclient_finish_command(c);
346 gint mpdclient_cmd_volume_up(struct mpdclient *c)
348         if (MPD_ERROR(c))
349                 return -1;
351         if (c->status == NULL ||
352             mpd_status_get_volume(c->status) == -1)
353                 return 0;
355         if (c->volume < 0)
356                 c->volume = mpd_status_get_volume(c->status);
358         if (c->volume >= 100)
359                 return 0;
361         return mpdclient_cmd_volume(c, ++c->volume);
364 gint mpdclient_cmd_volume_down(struct mpdclient *c)
366         if (MPD_ERROR(c))
367                 return -1;
369         if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
370                 return 0;
372         if (c->volume < 0)
373                 c->volume = mpd_status_get_volume(c->status);
375         if (c->volume <= 0)
376                 return 0;
378         return mpdclient_cmd_volume(c, --c->volume);
381 gint
382 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
384         if (MPD_ERROR(c))
385                 return -1;
387         mpd_send_add(c->connection, path_utf8);
388         return mpdclient_finish_command(c);
391 gint
392 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
394         gint retval = 0;
396         if (MPD_ERROR(c))
397                 return -1;
399         if (song == NULL)
400                 return -1;
402         /* send the add command to mpd */
403         mpd_send_add(c->connection, mpd_song_get_uri(song));
404         if( (retval=mpdclient_finish_command(c)) )
405                 return retval;
407 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
408         /* add the song to playlist */
409         playlist_append(&c->playlist, song);
411         /* increment the playlist id, so we don't retrieve a new playlist */
412         c->playlist.id++;
414         /* call playlist updated callback */
415         mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
416 #endif
418         return 0;
421 gint
422 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
424         gint retval = 0;
425         struct mpd_song *song;
427         if (MPD_ERROR(c))
428                 return -1;
430         if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
431                 return -1;
433         song = playlist_get(&c->playlist, idx);
435         /* send the delete command to mpd */
436         mpd_send_delete_id(c->connection, mpd_song_get_id(song));
437         if( (retval=mpdclient_finish_command(c)) )
438                 return retval;
440 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
441         /* increment the playlist id, so we don't retrieve a new playlist */
442         c->playlist.id++;
444         /* remove the song from the playlist */
445         playlist_remove_reuse(&c->playlist, idx);
447         /* call playlist updated callback */
448         mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song);
450         /* remove references to the song */
451         if (c->song == song)
452                 c->song = NULL;
454         mpd_song_free(song);
455 #endif
457         return 0;
460 gint
461 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
463         gint n;
464         struct mpd_song *song1, *song2;
466         if (MPD_ERROR(c))
467                 return -1;
469         if (old_index == new_index || new_index < 0 ||
470             (guint)new_index >= c->playlist.list->len)
471                 return -1;
473         song1 = playlist_get(&c->playlist, old_index);
474         song2 = playlist_get(&c->playlist, new_index);
476         /* send the move command to mpd */
477         mpd_send_swap_id(c->connection,
478                          mpd_song_get_id(song1), mpd_song_get_id(song2));
479         if( (n=mpdclient_finish_command(c)) )
480                 return n;
482 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
483         /* update the playlist */
484         playlist_swap(&c->playlist, old_index, new_index);
486         /* increment the playlist id, so we don't retrieve a new playlist */
487         c->playlist.id++;
488 #endif
490         /* call playlist updated callback */
491         mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index);
493         return 0;
496 gint
497 mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8)
499         gint retval = 0;
501         if (MPD_ERROR(c))
502                 return -1;
504         mpd_send_save(c->connection, filename_utf8);
505         if ((retval = mpdclient_finish_command(c)) == 0)
506                 mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL);
507         return retval;
510 gint
511 mpdclient_cmd_load_playlist(struct mpdclient *c, const gchar *filename_utf8)
513         if (MPD_ERROR(c))
514                 return -1;
516         mpd_send_load(c->connection, filename_utf8);
517         return mpdclient_finish_command(c);
520 gint
521 mpdclient_cmd_delete_playlist(struct mpdclient *c, const gchar *filename_utf8)
523         gint retval = 0;
525         if (MPD_ERROR(c))
526                 return -1;
528         mpd_send_rm(c->connection, filename_utf8);
529         if ((retval = mpdclient_finish_command(c)) == 0)
530                 mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL);
531         return retval;
535 /****************************************************************************/
536 /*** Callback management functions ******************************************/
537 /****************************************************************************/
539 static void
540 do_list_callbacks(struct mpdclient *c, GList *list, gint event, gpointer data)
542         while (list) {
543                 mpdc_list_cb_t fn = list->data;
545                 fn(c, event, data);
546                 list = list->next;
547         }
550 void
551 mpdclient_playlist_callback(struct mpdclient *c, int event, gpointer data)
553         do_list_callbacks(c, c->playlist_callbacks, event, data);
556 void
557 mpdclient_install_playlist_callback(struct mpdclient *c,mpdc_list_cb_t cb)
559         c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb);
562 void
563 mpdclient_remove_playlist_callback(struct mpdclient *c, mpdc_list_cb_t cb)
565         c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb);
568 void
569 mpdclient_browse_callback(struct mpdclient *c, int event, gpointer data)
571         do_list_callbacks(c, c->browse_callbacks, event, data);
575 void
576 mpdclient_install_browse_callback(struct mpdclient *c,mpdc_list_cb_t cb)
578         c->browse_callbacks = g_list_append(c->browse_callbacks, cb);
581 void
582 mpdclient_remove_browse_callback(struct mpdclient *c, mpdc_list_cb_t cb)
584         c->browse_callbacks = g_list_remove(c->browse_callbacks, cb);
587 void
588 mpdclient_install_error_callback(struct mpdclient *c, mpdc_error_cb_t cb)
590         c->error_callbacks = g_list_append(c->error_callbacks, cb);
593 void
594 mpdclient_remove_error_callback(struct mpdclient *c, mpdc_error_cb_t cb)
596         c->error_callbacks = g_list_remove(c->error_callbacks, cb);
600 /****************************************************************************/
601 /*** Playlist management functions ******************************************/
602 /****************************************************************************/
604 /* update playlist */
605 bool
606 mpdclient_playlist_update(struct mpdclient *c)
608         struct mpd_entity *entity;
610         if (MPD_ERROR(c))
611                 return false;
613         playlist_clear(&c->playlist);
615         mpd_send_list_queue_meta(c->connection);
616         while ((entity = mpd_recv_entity(c->connection))) {
617                 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
618                         playlist_append(&c->playlist, mpd_entity_get_song(entity));
620                 mpd_entity_free(entity);
621         }
623         c->playlist.id = mpd_status_get_queue_version(c->status);
624         c->song = NULL;
626         /* call playlist updated callbacks */
627         mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
629         return mpdclient_finish_command(c) == 0;
632 /* update playlist (plchanges) */
633 bool
634 mpdclient_playlist_update_changes(struct mpdclient *c)
636         struct mpd_song *song;
637         guint length;
639         if (MPD_ERROR(c))
640                 return false;
642         mpd_send_queue_changes_meta(c->connection, c->playlist.id);
644         while ((song = mpd_recv_song(c->connection)) != NULL) {
645                 int pos = mpd_song_get_pos(song);
647                 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
648                         /* update song */
649                         playlist_replace(&c->playlist, pos, song);
650                 } else {
651                         /* add a new song */
652                         playlist_append(&c->playlist, song);
653                 }
655                 mpd_song_free(song);
656         }
658         /* remove trailing songs */
660         length = mpd_status_get_queue_length(c->status);
661         while (length < c->playlist.list->len) {
662                 guint pos = c->playlist.list->len - 1;
664                 /* Remove the last playlist entry */
665                 playlist_remove(&c->playlist, pos);
666         }
668         c->song = NULL;
669         c->playlist.id = mpd_status_get_queue_version(c->status);
671         mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
673         return mpdclient_finish_command(c) == 0;
677 /****************************************************************************/
678 /*** Filelist functions *****************************************************/
679 /****************************************************************************/
681 struct filelist *
682 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
684         struct filelist *filelist;
685         struct mpd_entity *entity;
687         if (MPD_ERROR(c))
688                 return NULL;
690         mpd_send_list_meta(c->connection, path);
691         filelist = filelist_new();
692         if (path && path[0] && strcmp(path, "/"))
693                 /* add a dummy entry for ./.. */
694                 filelist_append(filelist, NULL);
696         while ((entity = mpd_recv_entity(c->connection)) != NULL)
697                 filelist_append(filelist, entity);
699         /* If there's an error, ignore it.  We'll return an empty filelist. */
700         mpdclient_finish_command(c);
702         filelist_sort_dir_play(filelist, compare_filelistentry);
704         return filelist;
707 static struct filelist *
708 mpdclient_recv_filelist_response(struct mpdclient *c)
710         struct filelist *filelist;
711         struct mpd_entity *entity;
713         filelist = filelist_new();
715         while ((entity = mpd_recv_entity(c->connection)) != NULL)
716                 filelist_append(filelist, entity);
718         if (mpdclient_finish_command(c)) {
719                 filelist_free(filelist);
720                 return NULL;
721         }
723         return filelist;
726 struct filelist *
727 mpdclient_filelist_search(struct mpdclient *c,
728                           int exact_match,
729                           enum mpd_tag_type tag,
730                           gchar *filter_utf8)
732         if (MPD_ERROR(c))
733                 return NULL;
735         mpd_search_db_songs(c->connection, exact_match);
736         mpd_search_add_tag_constraint(c->connection, MPD_OPERATOR_DEFAULT,
737                                       tag, filter_utf8);
738         mpd_search_commit(c->connection);
740         return mpdclient_recv_filelist_response(c);
743 int
744 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
746         guint i;
748         if (MPD_ERROR(c))
749                 return -1;
751         if (filelist_is_empty(fl))
752                 return 0;
754         mpd_command_list_begin(c->connection, false);
756         for (i = 0; i < filelist_length(fl); ++i) {
757                 struct filelist_entry *entry = filelist_get(fl, i);
758                 struct mpd_entity *entity  = entry->entity;
760                 if (entity != NULL &&
761                     mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
762                         const struct mpd_song *song =
763                                 mpd_entity_get_song(entity);
764                         const char *uri = mpd_song_get_uri(song);
766                         if (uri != NULL)
767                                 mpd_send_add(c->connection, uri);
768                 }
769         }
771         mpd_command_list_end(c->connection);
772         return mpdclient_finish_command(c);
775 GList *
776 mpdclient_get_artists(struct mpdclient *c)
778         GList *list = NULL;
779         struct mpd_pair *pair;
781         if (MPD_ERROR(c))
782                return NULL;
784         mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
785         mpd_search_commit(c->connection);
787         while ((pair = mpd_recv_pair_tag(c->connection,
788                                          MPD_TAG_ARTIST)) != 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;
799 GList *
800 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
802         GList *list = NULL;
803         struct mpd_pair *pair;
805         if (MPD_ERROR(c))
806                return NULL;
808         mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
809         if (artist_utf8 != NULL)
810                 mpd_search_add_tag_constraint(c->connection,
811                                               MPD_OPERATOR_DEFAULT,
812                                               MPD_TAG_ARTIST, artist_utf8);
813         mpd_search_commit(c->connection);
815         while ((pair = mpd_recv_pair_tag(c->connection,
816                                          MPD_TAG_ALBUM)) != NULL) {
817                 list = g_list_append(list, g_strdup(pair->value));
818                 mpd_return_pair(c->connection, pair);
819         }
821         if (mpdclient_finish_command(c))
822                 return string_list_free(list);
824         return list;