54ce2c7145b7dc4b95a7a277a54874337108f8eb
1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2009 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
20 #include "mpdclient.h"
21 #include "filelist.h"
22 #include "screen_client.h"
23 #include "config.h"
24 #include "options.h"
25 #include "strfsong.h"
26 #include "utils.h"
28 #include <mpd/client.h>
30 #include <stdlib.h>
31 #include <unistd.h>
32 #include <time.h>
33 #include <string.h>
35 #define BUFSIZE 1024
37 /* sort by list-format */
38 gint
39 compare_filelistentry_format(gconstpointer filelist_entry1,
40 gconstpointer filelist_entry2)
41 {
42 const struct mpd_entity *e1, *e2;
43 char key1[BUFSIZE], key2[BUFSIZE];
44 int n = 0;
46 e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
47 e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
49 if (e1 && e2 &&
50 mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
51 mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
52 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
53 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
54 n = strcmp(key1,key2);
55 }
57 return n;
58 }
61 /****************************************************************************/
62 /*** mpdclient functions ****************************************************/
63 /****************************************************************************/
65 bool
66 mpdclient_handle_error(struct mpdclient *c)
67 {
68 enum mpd_error error = mpd_connection_get_error(c->connection);
70 assert(error != MPD_ERROR_SUCCESS);
72 if (error == MPD_ERROR_SERVER &&
73 mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
74 screen_auth(c))
75 return true;
77 mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
79 if (!mpd_connection_clear_error(c->connection))
80 mpdclient_disconnect(c);
82 return false;
83 }
85 static bool
86 mpdclient_finish_command(struct mpdclient *c)
87 {
88 return mpd_response_finish(c->connection)
89 ? true : mpdclient_handle_error(c);
90 }
92 struct mpdclient *
93 mpdclient_new(void)
94 {
95 struct mpdclient *c;
97 c = g_new0(struct mpdclient, 1);
98 playlist_init(&c->playlist);
99 c->volume = -1;
100 c->events = 0;
102 return c;
103 }
105 void
106 mpdclient_free(struct mpdclient *c)
107 {
108 mpdclient_disconnect(c);
110 mpdclient_playlist_free(&c->playlist);
112 g_free(c);
113 }
115 void
116 mpdclient_disconnect(struct mpdclient *c)
117 {
118 if (c->connection)
119 mpd_connection_free(c->connection);
120 c->connection = NULL;
122 if (c->status)
123 mpd_status_free(c->status);
124 c->status = NULL;
126 playlist_clear(&c->playlist);
128 if (c->song)
129 c->song = NULL;
130 }
132 bool
133 mpdclient_connect(struct mpdclient *c,
134 const gchar *host,
135 gint port,
136 gfloat _timeout,
137 const gchar *password)
138 {
139 /* close any open connection */
140 if( c->connection )
141 mpdclient_disconnect(c);
143 /* connect to MPD */
144 c->connection = mpd_connection_new(host, port, _timeout * 1000);
145 if (c->connection == NULL)
146 g_error("Out of memory");
148 if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
149 mpdclient_handle_error(c);
150 mpdclient_disconnect(c);
151 return false;
152 }
154 /* send password */
155 if (password != NULL && !mpd_run_password(c->connection, password)) {
156 mpdclient_handle_error(c);
157 mpdclient_disconnect(c);
158 return false;
159 }
161 return true;
162 }
164 bool
165 mpdclient_update(struct mpdclient *c)
166 {
167 struct mpd_connection *connection = mpdclient_get_connection(c);
168 bool retval;
170 c->volume = -1;
172 if (connection == NULL)
173 return false;
175 /* always announce these options as long as we don't have real
176 "idle" support */
177 c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
179 /* free the old status */
180 if (c->status)
181 mpd_status_free(c->status);
183 /* retrieve new status */
184 c->status = mpd_run_status(connection);
185 if (c->status == NULL)
186 return mpdclient_handle_error(c);
188 if (c->update_id != mpd_status_get_update_id(c->status)) {
189 c->events |= MPD_IDLE_UPDATE;
191 if (c->update_id > 0)
192 c->events |= MPD_IDLE_DATABASE;
193 }
195 c->update_id = mpd_status_get_update_id(c->status);
197 if (c->volume != mpd_status_get_volume(c->status))
198 c->events |= MPD_IDLE_MIXER;
200 c->volume = mpd_status_get_volume(c->status);
202 /* check if the playlist needs an update */
203 if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
204 c->events |= MPD_IDLE_PLAYLIST;
206 if (!playlist_is_empty(&c->playlist))
207 retval = mpdclient_playlist_update_changes(c);
208 else
209 retval = mpdclient_playlist_update(c);
210 } else
211 retval = true;
213 /* update the current song */
214 if (!c->song || mpd_status_get_song_id(c->status)) {
215 c->song = playlist_get_song(&c->playlist,
216 mpd_status_get_song_pos(c->status));
217 }
219 return retval;
220 }
222 struct mpd_connection *
223 mpdclient_get_connection(struct mpdclient *c)
224 {
225 return c->connection;
226 }
228 void
229 mpdclient_put_connection(G_GNUC_UNUSED struct mpdclient *c)
230 {
231 }
234 /****************************************************************************/
235 /*** MPD Commands **********************************************************/
236 /****************************************************************************/
238 bool
239 mpdclient_cmd_play(struct mpdclient *c, gint idx)
240 {
241 struct mpd_connection *connection = mpdclient_get_connection(c);
242 const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
244 if (connection == NULL)
245 return false;
247 if (song)
248 mpd_send_play_id(connection, mpd_song_get_id(song));
249 else
250 mpd_send_play(connection);
252 return mpdclient_finish_command(c);
253 }
255 bool
256 mpdclient_cmd_crop(struct mpdclient *c)
257 {
258 struct mpd_connection *connection = mpdclient_get_connection(c);
259 struct mpd_status *status;
260 bool playing;
261 int length, current;
263 if (connection == NULL)
264 return false;
266 status = mpd_run_status(connection);
267 if (status == NULL)
268 return mpdclient_handle_error(c);
270 playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
271 mpd_status_get_state(status) == MPD_STATE_PAUSE;
272 length = mpd_status_get_queue_length(status);
273 current = mpd_status_get_song_pos(status);
275 mpd_status_free(status);
277 if (!playing || length < 2)
278 return true;
280 mpd_command_list_begin(connection, false);
282 while (--length >= 0)
283 if (length != current)
284 mpd_send_delete(connection, length);
286 mpd_command_list_end(connection);
288 return mpdclient_finish_command(c);
289 }
291 bool
292 mpdclient_cmd_clear(struct mpdclient *c)
293 {
294 struct mpd_connection *connection = mpdclient_get_connection(c);
295 bool retval;
297 if (connection == NULL)
298 return false;
300 mpd_send_clear(connection);
301 retval = mpdclient_finish_command(c);
303 if (retval)
304 c->events |= MPD_IDLE_PLAYLIST;
306 return retval;
307 }
309 bool
310 mpdclient_cmd_volume(struct mpdclient *c, gint value)
311 {
312 struct mpd_connection *connection = mpdclient_get_connection(c);
313 if (connection == NULL)
314 return false;
316 mpd_send_set_volume(connection, value);
317 return mpdclient_finish_command(c);
318 }
320 bool
321 mpdclient_cmd_volume_up(struct mpdclient *c)
322 {
323 struct mpd_connection *connection = mpdclient_get_connection(c);
324 if (connection == NULL)
325 return false;
327 if (c->status == NULL ||
328 mpd_status_get_volume(c->status) == -1)
329 return true;
331 if (c->volume < 0)
332 c->volume = mpd_status_get_volume(c->status);
334 if (c->volume >= 100)
335 return true;
337 return mpdclient_cmd_volume(c, ++c->volume);
338 }
340 bool
341 mpdclient_cmd_volume_down(struct mpdclient *c)
342 {
343 struct mpd_connection *connection = mpdclient_get_connection(c);
344 if (connection == NULL)
345 return false;
347 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
348 return true;
350 if (c->volume < 0)
351 c->volume = mpd_status_get_volume(c->status);
353 if (c->volume <= 0)
354 return true;
356 return mpdclient_cmd_volume(c, --c->volume);
357 }
359 bool
360 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
361 {
362 struct mpd_connection *connection = mpdclient_get_connection(c);
363 if (connection == NULL)
364 return false;
366 mpd_send_add(connection, path_utf8);
367 return mpdclient_finish_command(c);
368 }
370 bool
371 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
372 {
373 struct mpd_connection *connection = mpdclient_get_connection(c);
374 struct mpd_status *status;
375 struct mpd_song *new_song;
377 assert(c != NULL);
378 assert(song != NULL);
380 if (connection == NULL || c->status == NULL)
381 return false;
383 /* send the add command to mpd; at the same time, get the new
384 status (to verify the new playlist id) and the last song
385 (we hope that's the song we just added) */
387 if (!mpd_command_list_begin(connection, true) ||
388 !mpd_send_add(connection, mpd_song_get_uri(song)) ||
389 !mpd_send_status(connection) ||
390 !mpd_send_get_queue_song_pos(connection,
391 playlist_length(&c->playlist)) ||
392 !mpd_command_list_end(connection) ||
393 !mpd_response_next(connection))
394 return mpdclient_handle_error(c);
396 c->events |= MPD_IDLE_PLAYLIST;
398 status = mpd_recv_status(connection);
399 if (status != NULL) {
400 if (c->status != NULL)
401 mpd_status_free(c->status);
402 c->status = status;
403 }
405 if (!mpd_response_next(connection))
406 return mpdclient_handle_error(c);
408 new_song = mpd_recv_song(connection);
409 if (!mpd_response_finish(connection) || new_song == NULL) {
410 if (new_song != NULL)
411 mpd_song_free(new_song);
413 return mpd_connection_clear_error(connection) ||
414 mpdclient_handle_error(c);
415 }
417 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
418 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
419 /* the cheap route: match on the new playlist length
420 and its version, we can keep our local playlist
421 copy in sync */
422 c->playlist.version = mpd_status_get_queue_version(status);
424 /* the song we just received has the correct id;
425 append it to the local playlist */
426 playlist_append(&c->playlist, new_song);
427 }
429 mpd_song_free(new_song);
431 return true;
432 }
434 bool
435 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
436 {
437 struct mpd_connection *connection = mpdclient_get_connection(c);
438 const struct mpd_song *song;
439 struct mpd_status *status;
441 if (connection == NULL || c->status == NULL)
442 return false;
444 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
445 return false;
447 song = playlist_get(&c->playlist, idx);
449 /* send the delete command to mpd; at the same time, get the
450 new status (to verify the playlist id) */
452 if (!mpd_command_list_begin(connection, false) ||
453 !mpd_send_delete_id(connection, mpd_song_get_id(song)) ||
454 !mpd_send_status(connection) ||
455 !mpd_command_list_end(connection))
456 return mpdclient_handle_error(c);
458 c->events |= MPD_IDLE_PLAYLIST;
460 status = mpd_recv_status(connection);
461 if (status != NULL) {
462 if (c->status != NULL)
463 mpd_status_free(c->status);
464 c->status = status;
465 }
467 if (!mpd_response_finish(connection))
468 return mpdclient_handle_error(c);
470 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
471 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
472 /* the cheap route: match on the new playlist length
473 and its version, we can keep our local playlist
474 copy in sync */
475 c->playlist.version = mpd_status_get_queue_version(status);
477 /* remove the song from the local playlist */
478 playlist_remove(&c->playlist, idx);
480 /* remove references to the song */
481 if (c->song == song)
482 c->song = NULL;
483 }
485 return true;
486 }
488 /**
489 * Fallback for mpdclient_cmd_delete_range() on MPD older than 0.16.
490 * It emulates the "delete range" command with a list of simple
491 * "delete" commands.
492 */
493 static bool
494 mpdclient_cmd_delete_range_fallback(struct mpdclient *c,
495 unsigned start, unsigned end)
496 {
497 struct mpd_connection *connection = mpdclient_get_connection(c);
498 if (connection == NULL)
499 return false;
501 if (!mpd_command_list_begin(connection, false))
502 return mpdclient_handle_error(c);
504 for (; start < end; --end)
505 mpd_send_delete(connection, start);
507 if (!mpd_command_list_end(connection) ||
508 !mpd_response_finish(connection))
509 return mpdclient_handle_error(c);
511 return true;
512 }
514 bool
515 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
516 {
517 struct mpd_connection *connection;
518 struct mpd_status *status;
520 connection = mpdclient_get_connection(c);
521 if (connection == NULL)
522 return false;
524 if (mpd_connection_cmp_server_version(connection, 0, 16, 0) < 0)
525 return mpdclient_cmd_delete_range_fallback(c, start, end);
527 /* MPD 0.16 supports "delete" with a range argument */
529 /* send the delete command to mpd; at the same time, get the
530 new status (to verify the playlist id) */
532 if (!mpd_command_list_begin(connection, false) ||
533 !mpd_send_delete_range(connection, start, end) ||
534 !mpd_send_status(connection) ||
535 !mpd_command_list_end(connection))
536 return mpdclient_handle_error(c);
538 c->events |= MPD_IDLE_PLAYLIST;
540 status = mpd_recv_status(connection);
541 if (status != NULL) {
542 if (c->status != NULL)
543 mpd_status_free(c->status);
544 c->status = status;
545 }
547 if (!mpd_response_finish(connection))
548 return mpdclient_handle_error(c);
550 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
551 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
552 /* the cheap route: match on the new playlist length
553 and its version, we can keep our local playlist
554 copy in sync */
555 c->playlist.version = mpd_status_get_queue_version(status);
557 /* remove the song from the local playlist */
558 while (end > start) {
559 --end;
561 /* remove references to the song */
562 if (c->song == playlist_get(&c->playlist, end))
563 c->song = NULL;
565 playlist_remove(&c->playlist, end);
566 }
567 }
569 return true;
570 }
572 bool
573 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
574 {
575 struct mpd_connection *connection = mpdclient_get_connection(c);
576 const struct mpd_song *song1, *song2;
577 struct mpd_status *status;
579 if (connection == NULL)
580 return false;
582 if (old_index == new_index || new_index < 0 ||
583 (guint)new_index >= c->playlist.list->len)
584 return false;
586 song1 = playlist_get(&c->playlist, old_index);
587 song2 = playlist_get(&c->playlist, new_index);
589 /* send the delete command to mpd; at the same time, get the
590 new status (to verify the playlist id) */
592 if (!mpd_command_list_begin(connection, false) ||
593 !mpd_send_swap_id(connection, mpd_song_get_id(song1),
594 mpd_song_get_id(song2)) ||
595 !mpd_send_status(connection) ||
596 !mpd_command_list_end(connection))
597 return mpdclient_handle_error(c);
599 c->events |= MPD_IDLE_PLAYLIST;
601 status = mpd_recv_status(connection);
602 if (status != NULL) {
603 if (c->status != NULL)
604 mpd_status_free(c->status);
605 c->status = status;
606 }
608 if (!mpd_response_finish(connection))
609 return mpdclient_handle_error(c);
611 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
612 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
613 /* the cheap route: match on the new playlist length
614 and its version, we can keep our local playlist
615 copy in sync */
616 c->playlist.version = mpd_status_get_queue_version(status);
618 /* swap songs in the local playlist */
619 playlist_swap(&c->playlist, old_index, new_index);
620 }
622 return true;
623 }
626 /****************************************************************************/
627 /*** Playlist management functions ******************************************/
628 /****************************************************************************/
630 /* update playlist */
631 bool
632 mpdclient_playlist_update(struct mpdclient *c)
633 {
634 struct mpd_connection *connection = mpdclient_get_connection(c);
635 struct mpd_entity *entity;
637 if (connection == NULL)
638 return false;
640 playlist_clear(&c->playlist);
642 mpd_send_list_queue_meta(connection);
643 while ((entity = mpd_recv_entity(connection))) {
644 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
645 playlist_append(&c->playlist, mpd_entity_get_song(entity));
647 mpd_entity_free(entity);
648 }
650 c->playlist.version = mpd_status_get_queue_version(c->status);
651 c->song = NULL;
653 return mpdclient_finish_command(c);
654 }
656 /* update playlist (plchanges) */
657 bool
658 mpdclient_playlist_update_changes(struct mpdclient *c)
659 {
660 struct mpd_connection *connection = mpdclient_get_connection(c);
661 struct mpd_song *song;
662 guint length;
664 if (connection == NULL)
665 return false;
667 mpd_send_queue_changes_meta(connection, c->playlist.version);
669 while ((song = mpd_recv_song(connection)) != NULL) {
670 int pos = mpd_song_get_pos(song);
672 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
673 /* update song */
674 playlist_replace(&c->playlist, pos, song);
675 } else {
676 /* add a new song */
677 playlist_append(&c->playlist, song);
678 }
680 mpd_song_free(song);
681 }
683 /* remove trailing songs */
685 length = mpd_status_get_queue_length(c->status);
686 while (length < c->playlist.list->len) {
687 guint pos = c->playlist.list->len - 1;
689 /* Remove the last playlist entry */
690 playlist_remove(&c->playlist, pos);
691 }
693 c->song = NULL;
694 c->playlist.version = mpd_status_get_queue_version(c->status);
696 return mpdclient_finish_command(c);
697 }
700 /****************************************************************************/
701 /*** Filelist functions *****************************************************/
702 /****************************************************************************/
704 bool
705 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
706 {
707 struct mpd_connection *connection = mpdclient_get_connection(c);
708 guint i;
710 if (connection == NULL)
711 return false;
713 if (filelist_is_empty(fl))
714 return true;
716 mpd_command_list_begin(connection, false);
718 for (i = 0; i < filelist_length(fl); ++i) {
719 struct filelist_entry *entry = filelist_get(fl, i);
720 struct mpd_entity *entity = entry->entity;
722 if (entity != NULL &&
723 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
724 const struct mpd_song *song =
725 mpd_entity_get_song(entity);
727 mpd_send_add(connection, mpd_song_get_uri(song));
728 }
729 }
731 mpd_command_list_end(connection);
732 return mpdclient_finish_command(c);
733 }
735 GList *
736 mpdclient_get_artists(struct mpdclient *c)
737 {
738 struct mpd_connection *connection = mpdclient_get_connection(c);
739 GList *list = NULL;
740 struct mpd_pair *pair;
742 if (connection == NULL)
743 return NULL;
745 mpd_search_db_tags(connection, MPD_TAG_ARTIST);
746 mpd_search_commit(connection);
748 while ((pair = mpd_recv_pair_tag(connection,
749 MPD_TAG_ARTIST)) != NULL) {
750 list = g_list_append(list, g_strdup(pair->value));
751 mpd_return_pair(connection, pair);
752 }
754 if (!mpdclient_finish_command(c))
755 return string_list_free(list);
757 return list;
758 }
760 GList *
761 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
762 {
763 struct mpd_connection *connection = mpdclient_get_connection(c);
764 GList *list = NULL;
765 struct mpd_pair *pair;
767 if (connection == NULL)
768 return NULL;
770 mpd_search_db_tags(connection, MPD_TAG_ALBUM);
771 if (artist_utf8 != NULL)
772 mpd_search_add_tag_constraint(connection,
773 MPD_OPERATOR_DEFAULT,
774 MPD_TAG_ARTIST, artist_utf8);
775 mpd_search_commit(connection);
777 while ((pair = mpd_recv_pair_tag(connection,
778 MPD_TAG_ALBUM)) != NULL) {
779 list = g_list_append(list, g_strdup(pair->value));
780 mpd_return_pair(connection, pair);
781 }
783 if (!mpdclient_finish_command(c))
784 return string_list_free(list);
786 return list;
787 }