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"
27 #include "gidle.h"
29 #include <mpd/client.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <time.h>
34 #include <string.h>
36 #define BUFSIZE 1024
38 /* sort by list-format */
39 gint
40 compare_filelistentry_format(gconstpointer filelist_entry1,
41 gconstpointer filelist_entry2)
42 {
43 const struct mpd_entity *e1, *e2;
44 char key1[BUFSIZE], key2[BUFSIZE];
45 int n = 0;
47 e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
48 e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
50 if (e1 && e2 &&
51 mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
52 mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
53 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
54 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
55 n = strcmp(key1,key2);
56 }
58 return n;
59 }
62 /****************************************************************************/
63 /*** mpdclient functions ****************************************************/
64 /****************************************************************************/
66 bool
67 mpdclient_handle_error(struct mpdclient *c)
68 {
69 enum mpd_error error = mpd_connection_get_error(c->connection);
71 assert(error != MPD_ERROR_SUCCESS);
73 if (error == MPD_ERROR_SERVER &&
74 mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
75 screen_auth(c))
76 return true;
78 mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
80 if (!mpd_connection_clear_error(c->connection))
81 mpdclient_disconnect(c);
83 return false;
84 }
86 static bool
87 mpdclient_finish_command(struct mpdclient *c)
88 {
89 return mpd_response_finish(c->connection)
90 ? true : mpdclient_handle_error(c);
91 }
93 struct mpdclient *
94 mpdclient_new(void)
95 {
96 struct mpdclient *c;
98 c = g_new0(struct mpdclient, 1);
99 playlist_init(&c->playlist);
100 c->volume = -1;
101 c->events = 0;
103 return c;
104 }
106 void
107 mpdclient_free(struct mpdclient *c)
108 {
109 mpdclient_disconnect(c);
111 mpdclient_playlist_free(&c->playlist);
113 g_free(c);
114 }
116 void
117 mpdclient_disconnect(struct mpdclient *c)
118 {
119 if (c->source != NULL) {
120 mpd_glib_free(c->source);
121 c->source = NULL;
122 c->idle = false;
123 }
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;
137 }
139 bool
140 mpdclient_connect(struct mpdclient *c,
141 const gchar *host,
142 gint port,
143 gfloat _timeout,
144 const gchar *password)
145 {
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;
169 }
171 bool
172 mpdclient_update(struct mpdclient *c)
173 {
174 struct mpd_connection *connection = mpdclient_get_connection(c);
175 bool retval;
177 c->volume = -1;
179 if (connection == NULL)
180 return false;
182 /* always announce these options as long as we don't have
183 "idle" support */
184 if (c->source == NULL)
185 c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
187 /* free the old status */
188 if (c->status)
189 mpd_status_free(c->status);
191 /* retrieve new status */
192 c->status = mpd_run_status(connection);
193 if (c->status == NULL)
194 return mpdclient_handle_error(c);
196 if (c->source == NULL &&
197 c->update_id != mpd_status_get_update_id(c->status)) {
198 c->events |= MPD_IDLE_UPDATE;
200 if (c->update_id > 0)
201 c->events |= MPD_IDLE_DATABASE;
202 }
204 c->update_id = mpd_status_get_update_id(c->status);
206 if (c->source == NULL &&
207 c->volume != mpd_status_get_volume(c->status))
208 c->events |= MPD_IDLE_MIXER;
210 c->volume = mpd_status_get_volume(c->status);
212 /* check if the playlist needs an update */
213 if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
214 if (c->source == NULL)
215 c->events |= MPD_IDLE_PLAYLIST;
217 if (!playlist_is_empty(&c->playlist))
218 retval = mpdclient_playlist_update_changes(c);
219 else
220 retval = mpdclient_playlist_update(c);
221 } else
222 retval = true;
224 /* update the current song */
225 if (!c->song || mpd_status_get_song_id(c->status)) {
226 c->song = playlist_get_song(&c->playlist,
227 mpd_status_get_song_pos(c->status));
228 }
230 return retval;
231 }
233 struct mpd_connection *
234 mpdclient_get_connection(struct mpdclient *c)
235 {
236 if (c->source != NULL && c->idle) {
237 c->idle = false;
238 mpd_glib_leave(c->source);
239 }
241 return c->connection;
242 }
244 void
245 mpdclient_put_connection(struct mpdclient *c)
246 {
247 assert(c->source == NULL || c->connection != NULL);
249 if (c->source != NULL && !c->idle) {
250 c->idle = true;
251 mpd_glib_enter(c->source);
252 }
253 }
256 /****************************************************************************/
257 /*** MPD Commands **********************************************************/
258 /****************************************************************************/
260 bool
261 mpdclient_cmd_play(struct mpdclient *c, gint idx)
262 {
263 struct mpd_connection *connection = mpdclient_get_connection(c);
264 const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
266 if (connection == NULL)
267 return false;
269 if (song)
270 mpd_send_play_id(connection, mpd_song_get_id(song));
271 else
272 mpd_send_play(connection);
274 return mpdclient_finish_command(c);
275 }
277 bool
278 mpdclient_cmd_crop(struct mpdclient *c)
279 {
280 struct mpd_connection *connection = mpdclient_get_connection(c);
281 struct mpd_status *status;
282 bool playing;
283 int length, current;
285 if (connection == NULL)
286 return false;
288 status = mpd_run_status(connection);
289 if (status == NULL)
290 return mpdclient_handle_error(c);
292 playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
293 mpd_status_get_state(status) == MPD_STATE_PAUSE;
294 length = mpd_status_get_queue_length(status);
295 current = mpd_status_get_song_pos(status);
297 mpd_status_free(status);
299 if (!playing || length < 2)
300 return true;
302 mpd_command_list_begin(connection, false);
304 while (--length >= 0)
305 if (length != current)
306 mpd_send_delete(connection, length);
308 mpd_command_list_end(connection);
310 return mpdclient_finish_command(c);
311 }
313 bool
314 mpdclient_cmd_clear(struct mpdclient *c)
315 {
316 struct mpd_connection *connection = mpdclient_get_connection(c);
317 bool retval;
319 if (connection == NULL)
320 return false;
322 mpd_send_clear(connection);
323 retval = mpdclient_finish_command(c);
325 if (retval)
326 c->events |= MPD_IDLE_PLAYLIST;
328 return retval;
329 }
331 bool
332 mpdclient_cmd_volume(struct mpdclient *c, gint value)
333 {
334 struct mpd_connection *connection = mpdclient_get_connection(c);
335 if (connection == NULL)
336 return false;
338 mpd_send_set_volume(connection, value);
339 return mpdclient_finish_command(c);
340 }
342 bool
343 mpdclient_cmd_volume_up(struct mpdclient *c)
344 {
345 struct mpd_connection *connection = mpdclient_get_connection(c);
346 if (connection == NULL)
347 return false;
349 if (c->status == NULL ||
350 mpd_status_get_volume(c->status) == -1)
351 return true;
353 if (c->volume < 0)
354 c->volume = mpd_status_get_volume(c->status);
356 if (c->volume >= 100)
357 return true;
359 return mpdclient_cmd_volume(c, ++c->volume);
360 }
362 bool
363 mpdclient_cmd_volume_down(struct mpdclient *c)
364 {
365 struct mpd_connection *connection = mpdclient_get_connection(c);
366 if (connection == NULL)
367 return false;
369 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
370 return true;
372 if (c->volume < 0)
373 c->volume = mpd_status_get_volume(c->status);
375 if (c->volume <= 0)
376 return true;
378 return mpdclient_cmd_volume(c, --c->volume);
379 }
381 bool
382 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
383 {
384 struct mpd_connection *connection = mpdclient_get_connection(c);
385 if (connection == NULL)
386 return false;
388 mpd_send_add(connection, path_utf8);
389 return mpdclient_finish_command(c);
390 }
392 bool
393 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
394 {
395 struct mpd_connection *connection = mpdclient_get_connection(c);
396 struct mpd_status *status;
397 struct mpd_song *new_song;
399 assert(c != NULL);
400 assert(song != NULL);
402 if (connection == NULL || c->status == NULL)
403 return false;
405 /* send the add command to mpd; at the same time, get the new
406 status (to verify the new playlist id) and the last song
407 (we hope that's the song we just added) */
409 if (!mpd_command_list_begin(connection, true) ||
410 !mpd_send_add(connection, mpd_song_get_uri(song)) ||
411 !mpd_send_status(connection) ||
412 !mpd_send_get_queue_song_pos(connection,
413 playlist_length(&c->playlist)) ||
414 !mpd_command_list_end(connection) ||
415 !mpd_response_next(connection))
416 return mpdclient_handle_error(c);
418 c->events |= MPD_IDLE_PLAYLIST;
420 status = mpd_recv_status(connection);
421 if (status != NULL) {
422 if (c->status != NULL)
423 mpd_status_free(c->status);
424 c->status = status;
425 }
427 if (!mpd_response_next(connection))
428 return mpdclient_handle_error(c);
430 new_song = mpd_recv_song(connection);
431 if (!mpd_response_finish(connection) || new_song == NULL) {
432 if (new_song != NULL)
433 mpd_song_free(new_song);
435 return mpd_connection_clear_error(connection) ||
436 mpdclient_handle_error(c);
437 }
439 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
440 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
441 /* the cheap route: match on the new playlist length
442 and its version, we can keep our local playlist
443 copy in sync */
444 c->playlist.version = mpd_status_get_queue_version(status);
446 /* the song we just received has the correct id;
447 append it to the local playlist */
448 playlist_append(&c->playlist, new_song);
449 }
451 mpd_song_free(new_song);
453 return true;
454 }
456 bool
457 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
458 {
459 struct mpd_connection *connection = mpdclient_get_connection(c);
460 const struct mpd_song *song;
461 struct mpd_status *status;
463 if (connection == NULL || c->status == NULL)
464 return false;
466 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
467 return false;
469 song = playlist_get(&c->playlist, idx);
471 /* send the delete command to mpd; at the same time, get the
472 new status (to verify the playlist id) */
474 if (!mpd_command_list_begin(connection, false) ||
475 !mpd_send_delete_id(connection, mpd_song_get_id(song)) ||
476 !mpd_send_status(connection) ||
477 !mpd_command_list_end(connection))
478 return mpdclient_handle_error(c);
480 c->events |= MPD_IDLE_PLAYLIST;
482 status = mpd_recv_status(connection);
483 if (status != NULL) {
484 if (c->status != NULL)
485 mpd_status_free(c->status);
486 c->status = status;
487 }
489 if (!mpd_response_finish(connection))
490 return mpdclient_handle_error(c);
492 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
493 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
494 /* the cheap route: match on the new playlist length
495 and its version, we can keep our local playlist
496 copy in sync */
497 c->playlist.version = mpd_status_get_queue_version(status);
499 /* remove the song from the local playlist */
500 playlist_remove(&c->playlist, idx);
502 /* remove references to the song */
503 if (c->song == song)
504 c->song = NULL;
505 }
507 return true;
508 }
510 /**
511 * Fallback for mpdclient_cmd_delete_range() on MPD older than 0.16.
512 * It emulates the "delete range" command with a list of simple
513 * "delete" commands.
514 */
515 static bool
516 mpdclient_cmd_delete_range_fallback(struct mpdclient *c,
517 unsigned start, unsigned end)
518 {
519 struct mpd_connection *connection = mpdclient_get_connection(c);
520 if (connection == NULL)
521 return false;
523 if (!mpd_command_list_begin(connection, false))
524 return mpdclient_handle_error(c);
526 for (; start < end; --end)
527 mpd_send_delete(connection, start);
529 if (!mpd_command_list_end(connection) ||
530 !mpd_response_finish(connection))
531 return mpdclient_handle_error(c);
533 return true;
534 }
536 bool
537 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
538 {
539 struct mpd_connection *connection;
540 struct mpd_status *status;
542 connection = mpdclient_get_connection(c);
543 if (connection == NULL)
544 return false;
546 if (mpd_connection_cmp_server_version(connection, 0, 16, 0) < 0)
547 return mpdclient_cmd_delete_range_fallback(c, start, end);
549 /* MPD 0.16 supports "delete" with a range argument */
551 /* send the delete command to mpd; at the same time, get the
552 new status (to verify the playlist id) */
554 if (!mpd_command_list_begin(connection, false) ||
555 !mpd_send_delete_range(connection, start, end) ||
556 !mpd_send_status(connection) ||
557 !mpd_command_list_end(connection))
558 return mpdclient_handle_error(c);
560 c->events |= MPD_IDLE_PLAYLIST;
562 status = mpd_recv_status(connection);
563 if (status != NULL) {
564 if (c->status != NULL)
565 mpd_status_free(c->status);
566 c->status = status;
567 }
569 if (!mpd_response_finish(connection))
570 return mpdclient_handle_error(c);
572 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
573 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
574 /* the cheap route: match on the new playlist length
575 and its version, we can keep our local playlist
576 copy in sync */
577 c->playlist.version = mpd_status_get_queue_version(status);
579 /* remove the song from the local playlist */
580 while (end > start) {
581 --end;
583 /* remove references to the song */
584 if (c->song == playlist_get(&c->playlist, end))
585 c->song = NULL;
587 playlist_remove(&c->playlist, end);
588 }
589 }
591 return true;
592 }
594 bool
595 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
596 {
597 struct mpd_connection *connection = mpdclient_get_connection(c);
598 const struct mpd_song *song1, *song2;
599 struct mpd_status *status;
601 if (connection == NULL)
602 return false;
604 if (old_index == new_index || new_index < 0 ||
605 (guint)new_index >= c->playlist.list->len)
606 return false;
608 song1 = playlist_get(&c->playlist, old_index);
609 song2 = playlist_get(&c->playlist, new_index);
611 /* send the delete command to mpd; at the same time, get the
612 new status (to verify the playlist id) */
614 if (!mpd_command_list_begin(connection, false) ||
615 !mpd_send_swap_id(connection, mpd_song_get_id(song1),
616 mpd_song_get_id(song2)) ||
617 !mpd_send_status(connection) ||
618 !mpd_command_list_end(connection))
619 return mpdclient_handle_error(c);
621 c->events |= MPD_IDLE_PLAYLIST;
623 status = mpd_recv_status(connection);
624 if (status != NULL) {
625 if (c->status != NULL)
626 mpd_status_free(c->status);
627 c->status = status;
628 }
630 if (!mpd_response_finish(connection))
631 return mpdclient_handle_error(c);
633 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
634 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
635 /* the cheap route: match on the new playlist length
636 and its version, we can keep our local playlist
637 copy in sync */
638 c->playlist.version = mpd_status_get_queue_version(status);
640 /* swap songs in the local playlist */
641 playlist_swap(&c->playlist, old_index, new_index);
642 }
644 return true;
645 }
648 /****************************************************************************/
649 /*** Playlist management functions ******************************************/
650 /****************************************************************************/
652 /* update playlist */
653 bool
654 mpdclient_playlist_update(struct mpdclient *c)
655 {
656 struct mpd_connection *connection = mpdclient_get_connection(c);
657 struct mpd_entity *entity;
659 if (connection == NULL)
660 return false;
662 playlist_clear(&c->playlist);
664 mpd_send_list_queue_meta(connection);
665 while ((entity = mpd_recv_entity(connection))) {
666 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
667 playlist_append(&c->playlist, mpd_entity_get_song(entity));
669 mpd_entity_free(entity);
670 }
672 c->playlist.version = mpd_status_get_queue_version(c->status);
673 c->song = NULL;
675 return mpdclient_finish_command(c);
676 }
678 /* update playlist (plchanges) */
679 bool
680 mpdclient_playlist_update_changes(struct mpdclient *c)
681 {
682 struct mpd_connection *connection = mpdclient_get_connection(c);
683 struct mpd_song *song;
684 guint length;
686 if (connection == NULL)
687 return false;
689 mpd_send_queue_changes_meta(connection, c->playlist.version);
691 while ((song = mpd_recv_song(connection)) != NULL) {
692 int pos = mpd_song_get_pos(song);
694 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
695 /* update song */
696 playlist_replace(&c->playlist, pos, song);
697 } else {
698 /* add a new song */
699 playlist_append(&c->playlist, song);
700 }
702 mpd_song_free(song);
703 }
705 /* remove trailing songs */
707 length = mpd_status_get_queue_length(c->status);
708 while (length < c->playlist.list->len) {
709 guint pos = c->playlist.list->len - 1;
711 /* Remove the last playlist entry */
712 playlist_remove(&c->playlist, pos);
713 }
715 c->song = NULL;
716 c->playlist.version = mpd_status_get_queue_version(c->status);
718 return mpdclient_finish_command(c);
719 }
722 /****************************************************************************/
723 /*** Filelist functions *****************************************************/
724 /****************************************************************************/
726 bool
727 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
728 {
729 struct mpd_connection *connection = mpdclient_get_connection(c);
730 guint i;
732 if (connection == NULL)
733 return false;
735 if (filelist_is_empty(fl))
736 return true;
738 mpd_command_list_begin(connection, false);
740 for (i = 0; i < filelist_length(fl); ++i) {
741 struct filelist_entry *entry = filelist_get(fl, i);
742 struct mpd_entity *entity = entry->entity;
744 if (entity != NULL &&
745 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
746 const struct mpd_song *song =
747 mpd_entity_get_song(entity);
749 mpd_send_add(connection, mpd_song_get_uri(song));
750 }
751 }
753 mpd_command_list_end(connection);
754 return mpdclient_finish_command(c);
755 }
757 GList *
758 mpdclient_get_artists(struct mpdclient *c)
759 {
760 struct mpd_connection *connection = mpdclient_get_connection(c);
761 GList *list = NULL;
762 struct mpd_pair *pair;
764 if (connection == NULL)
765 return NULL;
767 mpd_search_db_tags(connection, MPD_TAG_ARTIST);
768 mpd_search_commit(connection);
770 while ((pair = mpd_recv_pair_tag(connection,
771 MPD_TAG_ARTIST)) != NULL) {
772 list = g_list_append(list, g_strdup(pair->value));
773 mpd_return_pair(connection, pair);
774 }
776 if (!mpdclient_finish_command(c))
777 return string_list_free(list);
779 return list;
780 }
782 GList *
783 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
784 {
785 struct mpd_connection *connection = mpdclient_get_connection(c);
786 GList *list = NULL;
787 struct mpd_pair *pair;
789 if (connection == NULL)
790 return NULL;
792 mpd_search_db_tags(connection, MPD_TAG_ALBUM);
793 if (artist_utf8 != NULL)
794 mpd_search_add_tag_constraint(connection,
795 MPD_OPERATOR_DEFAULT,
796 MPD_TAG_ARTIST, artist_utf8);
797 mpd_search_commit(connection);
799 while ((pair = mpd_recv_pair_tag(connection,
800 MPD_TAG_ALBUM)) != NULL) {
801 list = g_list_append(list, g_strdup(pair->value));
802 mpd_return_pair(connection, pair);
803 }
805 if (!mpdclient_finish_command(c))
806 return string_list_free(list);
808 return list;
809 }