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)
108 {
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 mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
123 if (!mpd_connection_clear_error(c->connection))
124 mpdclient_disconnect(c);
126 return error;
127 }
129 static gint
130 mpdclient_finish_command(struct mpdclient *c)
131 {
132 return mpd_response_finish(c->connection)
133 ? 0 : mpdclient_handle_error(c);
134 }
136 struct mpdclient *
137 mpdclient_new(void)
138 {
139 struct mpdclient *c;
141 c = g_new0(struct mpdclient, 1);
142 playlist_init(&c->playlist);
143 c->volume = -1;
144 c->events = 0;
146 return c;
147 }
149 void
150 mpdclient_free(struct mpdclient *c)
151 {
152 mpdclient_disconnect(c);
154 mpdclient_playlist_free(&c->playlist);
156 g_list_free(c->playlist_callbacks);
157 g_list_free(c->browse_callbacks);
158 g_free(c);
159 }
161 void
162 mpdclient_disconnect(struct mpdclient *c)
163 {
164 if (c->connection)
165 mpd_connection_free(c->connection);
166 c->connection = NULL;
168 if (c->status)
169 mpd_status_free(c->status);
170 c->status = NULL;
172 playlist_clear(&c->playlist);
174 if (c->song)
175 c->song = NULL;
176 }
178 bool
179 mpdclient_connect(struct mpdclient *c,
180 const gchar *host,
181 gint port,
182 gfloat _timeout,
183 const gchar *password)
184 {
185 /* close any open connection */
186 if( c->connection )
187 mpdclient_disconnect(c);
189 /* connect to MPD */
190 c->connection = mpd_connection_new(host, port, _timeout * 1000);
191 if (c->connection == NULL)
192 g_error("Out of memory");
194 if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
195 mpdclient_handle_error(c);
196 mpdclient_disconnect(c);
197 return false;
198 }
200 /* send password */
201 if (password != NULL && !mpd_run_password(c->connection, password)) {
202 mpdclient_handle_error(c);
203 mpdclient_disconnect(c);
204 return false;
205 }
207 return true;
208 }
210 bool
211 mpdclient_update(struct mpdclient *c)
212 {
213 bool retval;
215 c->volume = -1;
217 if (MPD_ERROR(c))
218 return false;
220 /* always announce these options as long as we don't have real
221 "idle" support */
222 c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
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 != mpd_status_get_update_id(c->status)) {
234 c->events |= MPD_IDLE_UPDATE;
236 if (c->update_id > 0)
237 c->events |= MPD_IDLE_DATABASE;
238 }
240 if (c->update_id > 0 &&
241 c->update_id != mpd_status_get_update_id(c->status))
242 mpdclient_browse_callback(c, BROWSE_DB_UPDATED, NULL);
244 c->update_id = mpd_status_get_update_id(c->status);
246 if (c->volume != mpd_status_get_volume(c->status))
247 c->events |= MPD_IDLE_MIXER;
249 c->volume = mpd_status_get_volume(c->status);
251 /* check if the playlist needs an update */
252 if (c->playlist.id != mpd_status_get_queue_version(c->status)) {
253 c->events |= MPD_IDLE_PLAYLIST;
255 if (!playlist_is_empty(&c->playlist))
256 retval = mpdclient_playlist_update_changes(c);
257 else
258 retval = mpdclient_playlist_update(c);
259 } else
260 retval = true;
262 /* update the current song */
263 if (!c->song || mpd_status_get_song_id(c->status)) {
264 c->song = playlist_get_song(&c->playlist,
265 mpd_status_get_song_pos(c->status));
266 }
268 return retval;
269 }
272 /****************************************************************************/
273 /*** MPD Commands **********************************************************/
274 /****************************************************************************/
276 gint
277 mpdclient_cmd_play(struct mpdclient *c, gint idx)
278 {
279 const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
281 if (MPD_ERROR(c))
282 return -1;
284 if (song)
285 mpd_send_play_id(c->connection, mpd_song_get_id(song));
286 else
287 mpd_send_play(c->connection);
289 return mpdclient_finish_command(c);
290 }
292 gint
293 mpdclient_cmd_crop(struct mpdclient *c)
294 {
295 struct mpd_status *status;
296 bool playing;
297 int length, current;
299 if (MPD_ERROR(c))
300 return -1;
302 status = mpd_run_status(c->connection);
303 if (status == NULL)
304 return mpdclient_handle_error(c);
306 playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
307 mpd_status_get_state(status) == MPD_STATE_PAUSE;
308 length = mpd_status_get_queue_length(status);
309 current = mpd_status_get_song_pos(status);
311 mpd_status_free(status);
313 if (!playing || length < 2)
314 return 0;
316 mpd_command_list_begin(c->connection, false);
318 while (--length >= 0)
319 if (length != current)
320 mpd_send_delete(c->connection, length);
322 mpd_command_list_end(c->connection);
324 return mpdclient_finish_command(c);
325 }
327 gint
328 mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end)
329 {
330 mpd_send_shuffle_range(c->connection, start, end);
331 return mpdclient_finish_command(c);
332 }
334 gint
335 mpdclient_cmd_clear(struct mpdclient *c)
336 {
337 gint retval = 0;
339 if (MPD_ERROR(c))
340 return -1;
342 mpd_send_clear(c->connection);
343 retval = mpdclient_finish_command(c);
344 /* call playlist updated callback */
345 mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL);
346 return retval;
347 }
349 gint
350 mpdclient_cmd_volume(struct mpdclient *c, gint value)
351 {
352 if (MPD_ERROR(c))
353 return -1;
355 mpd_send_set_volume(c->connection, value);
356 return mpdclient_finish_command(c);
357 }
359 gint mpdclient_cmd_volume_up(struct mpdclient *c)
360 {
361 if (MPD_ERROR(c))
362 return -1;
364 if (c->status == NULL ||
365 mpd_status_get_volume(c->status) == -1)
366 return 0;
368 if (c->volume < 0)
369 c->volume = mpd_status_get_volume(c->status);
371 if (c->volume >= 100)
372 return 0;
374 return mpdclient_cmd_volume(c, ++c->volume);
375 }
377 gint mpdclient_cmd_volume_down(struct mpdclient *c)
378 {
379 if (MPD_ERROR(c))
380 return -1;
382 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
383 return 0;
385 if (c->volume < 0)
386 c->volume = mpd_status_get_volume(c->status);
388 if (c->volume <= 0)
389 return 0;
391 return mpdclient_cmd_volume(c, --c->volume);
392 }
394 gint
395 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
396 {
397 if (MPD_ERROR(c))
398 return -1;
400 mpd_send_add(c->connection, path_utf8);
401 return mpdclient_finish_command(c);
402 }
404 gint
405 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
406 {
407 gint retval = 0;
409 if (MPD_ERROR(c))
410 return -1;
412 if (song == NULL)
413 return -1;
415 /* send the add command to mpd */
416 mpd_send_add(c->connection, mpd_song_get_uri(song));
417 if( (retval=mpdclient_finish_command(c)) )
418 return retval;
420 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
421 /* add the song to playlist */
422 playlist_append(&c->playlist, song);
424 /* increment the playlist id, so we don't retrieve a new playlist */
425 c->playlist.id++;
427 /* call playlist updated callback */
428 mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
429 #endif
431 return 0;
432 }
434 gint
435 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
436 {
437 gint retval = 0;
438 struct mpd_song *song;
440 if (MPD_ERROR(c))
441 return -1;
443 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
444 return -1;
446 song = playlist_get(&c->playlist, idx);
448 /* send the delete command to mpd */
449 mpd_send_delete_id(c->connection, mpd_song_get_id(song));
450 if( (retval=mpdclient_finish_command(c)) )
451 return retval;
453 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
454 /* increment the playlist id, so we don't retrieve a new playlist */
455 c->playlist.id++;
457 /* remove the song from the playlist */
458 playlist_remove_reuse(&c->playlist, idx);
460 /* call playlist updated callback */
461 mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song);
463 /* remove references to the song */
464 if (c->song == song)
465 c->song = NULL;
467 mpd_song_free(song);
468 #endif
470 return 0;
471 }
473 gint
474 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
475 {
476 gint n;
477 struct mpd_song *song1, *song2;
479 if (MPD_ERROR(c))
480 return -1;
482 if (old_index == new_index || new_index < 0 ||
483 (guint)new_index >= c->playlist.list->len)
484 return -1;
486 song1 = playlist_get(&c->playlist, old_index);
487 song2 = playlist_get(&c->playlist, new_index);
489 /* send the move command to mpd */
490 mpd_send_swap_id(c->connection,
491 mpd_song_get_id(song1), mpd_song_get_id(song2));
492 if( (n=mpdclient_finish_command(c)) )
493 return n;
495 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
496 /* update the playlist */
497 playlist_swap(&c->playlist, old_index, new_index);
499 /* increment the playlist id, so we don't retrieve a new playlist */
500 c->playlist.id++;
501 #endif
503 /* call playlist updated callback */
504 mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index);
506 return 0;
507 }
509 gint
510 mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8)
511 {
512 gint retval = 0;
514 if (MPD_ERROR(c))
515 return -1;
517 mpd_send_save(c->connection, filename_utf8);
518 if ((retval = mpdclient_finish_command(c)) == 0) {
519 mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL);
520 c->events |= MPD_IDLE_STORED_PLAYLIST;
521 }
523 return retval;
524 }
526 gint
527 mpdclient_cmd_load_playlist(struct mpdclient *c, const gchar *filename_utf8)
528 {
529 if (MPD_ERROR(c))
530 return -1;
532 mpd_send_load(c->connection, filename_utf8);
533 return mpdclient_finish_command(c);
534 }
536 gint
537 mpdclient_cmd_delete_playlist(struct mpdclient *c, const gchar *filename_utf8)
538 {
539 gint retval = 0;
541 if (MPD_ERROR(c))
542 return -1;
544 mpd_send_rm(c->connection, filename_utf8);
545 if ((retval = mpdclient_finish_command(c)) == 0) {
546 mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL);
547 c->events |= MPD_IDLE_STORED_PLAYLIST;
548 }
550 return retval;
551 }
554 /****************************************************************************/
555 /*** Callback management functions ******************************************/
556 /****************************************************************************/
558 static void
559 do_list_callbacks(struct mpdclient *c, GList *list, gint event, gpointer data)
560 {
561 while (list) {
562 mpdc_list_cb_t fn = list->data;
564 fn(c, event, data);
565 list = list->next;
566 }
567 }
569 void
570 mpdclient_playlist_callback(struct mpdclient *c, int event, gpointer data)
571 {
572 do_list_callbacks(c, c->playlist_callbacks, event, data);
573 }
575 void
576 mpdclient_install_playlist_callback(struct mpdclient *c,mpdc_list_cb_t cb)
577 {
578 c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb);
579 }
581 void
582 mpdclient_remove_playlist_callback(struct mpdclient *c, mpdc_list_cb_t cb)
583 {
584 c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb);
585 }
587 void
588 mpdclient_browse_callback(struct mpdclient *c, int event, gpointer data)
589 {
590 do_list_callbacks(c, c->browse_callbacks, event, data);
591 }
594 void
595 mpdclient_install_browse_callback(struct mpdclient *c,mpdc_list_cb_t cb)
596 {
597 c->browse_callbacks = g_list_append(c->browse_callbacks, cb);
598 }
600 void
601 mpdclient_remove_browse_callback(struct mpdclient *c, mpdc_list_cb_t cb)
602 {
603 c->browse_callbacks = g_list_remove(c->browse_callbacks, cb);
604 }
607 /****************************************************************************/
608 /*** Playlist management functions ******************************************/
609 /****************************************************************************/
611 /* update playlist */
612 bool
613 mpdclient_playlist_update(struct mpdclient *c)
614 {
615 struct mpd_entity *entity;
617 if (MPD_ERROR(c))
618 return false;
620 playlist_clear(&c->playlist);
622 mpd_send_list_queue_meta(c->connection);
623 while ((entity = mpd_recv_entity(c->connection))) {
624 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
625 playlist_append(&c->playlist, mpd_entity_get_song(entity));
627 mpd_entity_free(entity);
628 }
630 c->playlist.id = mpd_status_get_queue_version(c->status);
631 c->song = NULL;
633 /* call playlist updated callbacks */
634 mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
636 return mpdclient_finish_command(c) == 0;
637 }
639 /* update playlist (plchanges) */
640 bool
641 mpdclient_playlist_update_changes(struct mpdclient *c)
642 {
643 struct mpd_song *song;
644 guint length;
646 if (MPD_ERROR(c))
647 return false;
649 mpd_send_queue_changes_meta(c->connection, c->playlist.id);
651 while ((song = mpd_recv_song(c->connection)) != NULL) {
652 int pos = mpd_song_get_pos(song);
654 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
655 /* update song */
656 playlist_replace(&c->playlist, pos, song);
657 } else {
658 /* add a new song */
659 playlist_append(&c->playlist, song);
660 }
662 mpd_song_free(song);
663 }
665 /* remove trailing songs */
667 length = mpd_status_get_queue_length(c->status);
668 while (length < c->playlist.list->len) {
669 guint pos = c->playlist.list->len - 1;
671 /* Remove the last playlist entry */
672 playlist_remove(&c->playlist, pos);
673 }
675 c->song = NULL;
676 c->playlist.id = mpd_status_get_queue_version(c->status);
678 mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
680 return mpdclient_finish_command(c) == 0;
681 }
684 /****************************************************************************/
685 /*** Filelist functions *****************************************************/
686 /****************************************************************************/
688 struct filelist *
689 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
690 {
691 struct filelist *filelist;
692 struct mpd_entity *entity;
694 if (MPD_ERROR(c))
695 return NULL;
697 mpd_send_list_meta(c->connection, path);
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 filelist_sort_dir_play(filelist, compare_filelistentry);
710 return filelist;
711 }
713 static struct filelist *
714 mpdclient_recv_filelist_response(struct mpdclient *c)
715 {
716 struct filelist *filelist;
717 struct mpd_entity *entity;
719 filelist = filelist_new();
721 while ((entity = mpd_recv_entity(c->connection)) != NULL)
722 filelist_append(filelist, entity);
724 if (mpdclient_finish_command(c)) {
725 filelist_free(filelist);
726 return NULL;
727 }
729 return filelist;
730 }
732 struct filelist *
733 mpdclient_filelist_search(struct mpdclient *c,
734 int exact_match,
735 enum mpd_tag_type tag,
736 gchar *filter_utf8)
737 {
738 if (MPD_ERROR(c))
739 return NULL;
741 mpd_search_db_songs(c->connection, exact_match);
742 mpd_search_add_tag_constraint(c->connection, MPD_OPERATOR_DEFAULT,
743 tag, filter_utf8);
744 mpd_search_commit(c->connection);
746 return mpdclient_recv_filelist_response(c);
747 }
749 int
750 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
751 {
752 guint i;
754 if (MPD_ERROR(c))
755 return -1;
757 if (filelist_is_empty(fl))
758 return 0;
760 mpd_command_list_begin(c->connection, false);
762 for (i = 0; i < filelist_length(fl); ++i) {
763 struct filelist_entry *entry = filelist_get(fl, i);
764 struct mpd_entity *entity = entry->entity;
766 if (entity != NULL &&
767 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
768 const struct mpd_song *song =
769 mpd_entity_get_song(entity);
770 const char *uri = mpd_song_get_uri(song);
772 if (uri != NULL)
773 mpd_send_add(c->connection, uri);
774 }
775 }
777 mpd_command_list_end(c->connection);
778 return mpdclient_finish_command(c);
779 }
781 GList *
782 mpdclient_get_artists(struct mpdclient *c)
783 {
784 GList *list = NULL;
785 struct mpd_pair *pair;
787 if (MPD_ERROR(c))
788 return NULL;
790 mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
791 mpd_search_commit(c->connection);
793 while ((pair = mpd_recv_pair_tag(c->connection,
794 MPD_TAG_ARTIST)) != NULL) {
795 list = g_list_append(list, g_strdup(pair->value));
796 mpd_return_pair(c->connection, pair);
797 }
799 if (mpdclient_finish_command(c))
800 return string_list_free(list);
802 return list;
803 }
805 GList *
806 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
807 {
808 GList *list = NULL;
809 struct mpd_pair *pair;
811 if (MPD_ERROR(c))
812 return NULL;
814 mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
815 if (artist_utf8 != NULL)
816 mpd_search_add_tag_constraint(c->connection,
817 MPD_OPERATOR_DEFAULT,
818 MPD_TAG_ARTIST, artist_utf8);
819 mpd_search_commit(c->connection);
821 while ((pair = mpd_recv_pair_tag(c->connection,
822 MPD_TAG_ALBUM)) != NULL) {
823 list = g_list_append(list, g_strdup(pair->value));
824 mpd_return_pair(c->connection, pair);
825 }
827 if (mpdclient_finish_command(c))
828 return string_list_free(list);
830 return list;
831 }