d8557159ffb7e9fa667285e4a1a964ffd7cf58d2
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 static bool
38 MPD_ERROR(const struct mpdclient *client)
39 {
40 return !mpdclient_is_connected(client) ||
41 mpd_connection_get_error(client->connection) != MPD_ERROR_SUCCESS;
42 }
44 /* sort by list-format */
45 gint
46 compare_filelistentry_format(gconstpointer filelist_entry1,
47 gconstpointer filelist_entry2)
48 {
49 const struct mpd_entity *e1, *e2;
50 char key1[BUFSIZE], key2[BUFSIZE];
51 int n = 0;
53 e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
54 e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
56 if (e1 && e2 &&
57 mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
58 mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
59 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
60 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
61 n = strcmp(key1,key2);
62 }
64 return n;
65 }
68 /****************************************************************************/
69 /*** mpdclient functions ****************************************************/
70 /****************************************************************************/
72 bool
73 mpdclient_handle_error(struct mpdclient *c)
74 {
75 enum mpd_error error = mpd_connection_get_error(c->connection);
77 assert(error != MPD_ERROR_SUCCESS);
79 if (error == MPD_ERROR_SERVER &&
80 mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
81 screen_auth(c))
82 return true;
84 mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
86 if (!mpd_connection_clear_error(c->connection))
87 mpdclient_disconnect(c);
89 return false;
90 }
92 static bool
93 mpdclient_finish_command(struct mpdclient *c)
94 {
95 return mpd_response_finish(c->connection)
96 ? true : mpdclient_handle_error(c);
97 }
99 struct mpdclient *
100 mpdclient_new(void)
101 {
102 struct mpdclient *c;
104 c = g_new0(struct mpdclient, 1);
105 playlist_init(&c->playlist);
106 c->volume = -1;
107 c->events = 0;
109 return c;
110 }
112 void
113 mpdclient_free(struct mpdclient *c)
114 {
115 mpdclient_disconnect(c);
117 mpdclient_playlist_free(&c->playlist);
119 g_free(c);
120 }
122 void
123 mpdclient_disconnect(struct mpdclient *c)
124 {
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 bool retval;
176 c->volume = -1;
178 if (MPD_ERROR(c))
179 return false;
181 /* always announce these options as long as we don't have real
182 "idle" support */
183 c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
185 /* free the old status */
186 if (c->status)
187 mpd_status_free(c->status);
189 /* retrieve new status */
190 c->status = mpd_run_status(c->connection);
191 if (c->status == NULL)
192 return mpdclient_handle_error(c);
194 if (c->update_id != mpd_status_get_update_id(c->status)) {
195 c->events |= MPD_IDLE_UPDATE;
197 if (c->update_id > 0)
198 c->events |= MPD_IDLE_DATABASE;
199 }
201 c->update_id = mpd_status_get_update_id(c->status);
203 if (c->volume != mpd_status_get_volume(c->status))
204 c->events |= MPD_IDLE_MIXER;
206 c->volume = mpd_status_get_volume(c->status);
208 /* check if the playlist needs an update */
209 if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
210 c->events |= MPD_IDLE_PLAYLIST;
212 if (!playlist_is_empty(&c->playlist))
213 retval = mpdclient_playlist_update_changes(c);
214 else
215 retval = mpdclient_playlist_update(c);
216 } else
217 retval = true;
219 /* update the current song */
220 if (!c->song || mpd_status_get_song_id(c->status)) {
221 c->song = playlist_get_song(&c->playlist,
222 mpd_status_get_song_pos(c->status));
223 }
225 return retval;
226 }
229 /****************************************************************************/
230 /*** MPD Commands **********************************************************/
231 /****************************************************************************/
233 bool
234 mpdclient_cmd_play(struct mpdclient *c, gint idx)
235 {
236 const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
238 if (MPD_ERROR(c))
239 return false;
241 if (song)
242 mpd_send_play_id(c->connection, mpd_song_get_id(song));
243 else
244 mpd_send_play(c->connection);
246 return mpdclient_finish_command(c);
247 }
249 bool
250 mpdclient_cmd_crop(struct mpdclient *c)
251 {
252 struct mpd_status *status;
253 bool playing;
254 int length, current;
256 if (MPD_ERROR(c))
257 return false;
259 status = mpd_run_status(c->connection);
260 if (status == NULL)
261 return mpdclient_handle_error(c);
263 playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
264 mpd_status_get_state(status) == MPD_STATE_PAUSE;
265 length = mpd_status_get_queue_length(status);
266 current = mpd_status_get_song_pos(status);
268 mpd_status_free(status);
270 if (!playing || length < 2)
271 return true;
273 mpd_command_list_begin(c->connection, false);
275 while (--length >= 0)
276 if (length != current)
277 mpd_send_delete(c->connection, length);
279 mpd_command_list_end(c->connection);
281 return mpdclient_finish_command(c);
282 }
284 bool
285 mpdclient_cmd_clear(struct mpdclient *c)
286 {
287 bool retval;
289 if (MPD_ERROR(c))
290 return false;
292 mpd_send_clear(c->connection);
293 retval = mpdclient_finish_command(c);
295 if (retval)
296 c->events |= MPD_IDLE_PLAYLIST;
298 return retval;
299 }
301 bool
302 mpdclient_cmd_volume(struct mpdclient *c, gint value)
303 {
304 if (MPD_ERROR(c))
305 return false;
307 mpd_send_set_volume(c->connection, value);
308 return mpdclient_finish_command(c);
309 }
311 bool
312 mpdclient_cmd_volume_up(struct mpdclient *c)
313 {
314 if (MPD_ERROR(c))
315 return false;
317 if (c->status == NULL ||
318 mpd_status_get_volume(c->status) == -1)
319 return true;
321 if (c->volume < 0)
322 c->volume = mpd_status_get_volume(c->status);
324 if (c->volume >= 100)
325 return true;
327 return mpdclient_cmd_volume(c, ++c->volume);
328 }
330 bool
331 mpdclient_cmd_volume_down(struct mpdclient *c)
332 {
333 if (MPD_ERROR(c))
334 return false;
336 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
337 return true;
339 if (c->volume < 0)
340 c->volume = mpd_status_get_volume(c->status);
342 if (c->volume <= 0)
343 return true;
345 return mpdclient_cmd_volume(c, --c->volume);
346 }
348 bool
349 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
350 {
351 if (MPD_ERROR(c))
352 return false;
354 mpd_send_add(c->connection, path_utf8);
355 return mpdclient_finish_command(c);
356 }
358 bool
359 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
360 {
361 struct mpd_status *status;
362 struct mpd_song *new_song;
364 assert(c != NULL);
365 assert(song != NULL);
367 if (MPD_ERROR(c) || c->status == NULL)
368 return false;
370 /* send the add command to mpd; at the same time, get the new
371 status (to verify the new playlist id) and the last song
372 (we hope that's the song we just added) */
374 if (!mpd_command_list_begin(c->connection, true) ||
375 !mpd_send_add(c->connection, mpd_song_get_uri(song)) ||
376 !mpd_send_status(c->connection) ||
377 !mpd_send_get_queue_song_pos(c->connection,
378 playlist_length(&c->playlist)) ||
379 !mpd_command_list_end(c->connection) ||
380 !mpd_response_next(c->connection))
381 return mpdclient_handle_error(c);
383 c->events |= MPD_IDLE_PLAYLIST;
385 status = mpd_recv_status(c->connection);
386 if (status != NULL) {
387 if (c->status != NULL)
388 mpd_status_free(c->status);
389 c->status = status;
390 }
392 if (!mpd_response_next(c->connection))
393 return mpdclient_handle_error(c);
395 new_song = mpd_recv_song(c->connection);
396 if (!mpd_response_finish(c->connection) || new_song == NULL) {
397 if (new_song != NULL)
398 mpd_song_free(new_song);
400 return mpd_connection_clear_error(c->connection) ||
401 mpdclient_handle_error(c);
402 }
404 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
405 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
406 /* the cheap route: match on the new playlist length
407 and its version, we can keep our local playlist
408 copy in sync */
409 c->playlist.version = mpd_status_get_queue_version(status);
411 /* the song we just received has the correct id;
412 append it to the local playlist */
413 playlist_append(&c->playlist, new_song);
414 }
416 mpd_song_free(new_song);
418 return true;
419 }
421 bool
422 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
423 {
424 const struct mpd_song *song;
425 struct mpd_status *status;
427 if (MPD_ERROR(c) || c->status == NULL)
428 return false;
430 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
431 return false;
433 song = playlist_get(&c->playlist, idx);
435 /* send the delete command to mpd; at the same time, get the
436 new status (to verify the playlist id) */
438 if (!mpd_command_list_begin(c->connection, false) ||
439 !mpd_send_delete_id(c->connection, mpd_song_get_id(song)) ||
440 !mpd_send_status(c->connection) ||
441 !mpd_command_list_end(c->connection))
442 return mpdclient_handle_error(c);
444 c->events |= MPD_IDLE_PLAYLIST;
446 status = mpd_recv_status(c->connection);
447 if (status != NULL) {
448 if (c->status != NULL)
449 mpd_status_free(c->status);
450 c->status = status;
451 }
453 if (!mpd_response_finish(c->connection))
454 return mpdclient_handle_error(c);
456 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
457 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
458 /* the cheap route: match on the new playlist length
459 and its version, we can keep our local playlist
460 copy in sync */
461 c->playlist.version = mpd_status_get_queue_version(status);
463 /* remove the song from the local playlist */
464 playlist_remove(&c->playlist, idx);
466 /* remove references to the song */
467 if (c->song == song)
468 c->song = NULL;
469 }
471 return true;
472 }
474 /**
475 * Fallback for mpdclient_cmd_delete_range() on MPD older than 0.16.
476 * It emulates the "delete range" command with a list of simple
477 * "delete" commands.
478 */
479 static bool
480 mpdclient_cmd_delete_range_fallback(struct mpdclient *c,
481 unsigned start, unsigned end)
482 {
483 if (!mpd_command_list_begin(c->connection, false))
484 return mpdclient_handle_error(c);
486 for (; start < end; --end)
487 mpd_send_delete(c->connection, start);
489 if (!mpd_command_list_end(c->connection) ||
490 !mpd_response_finish(c->connection))
491 return mpdclient_handle_error(c);
493 return true;
494 }
496 bool
497 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
498 {
499 struct mpd_status *status;
501 if (MPD_ERROR(c))
502 return false;
504 if (mpd_connection_cmp_server_version(c->connection, 0, 16, 0) < 0)
505 return mpdclient_cmd_delete_range_fallback(c, start, end);
507 /* MPD 0.16 supports "delete" with a range argument */
509 /* send the delete command to mpd; at the same time, get the
510 new status (to verify the playlist id) */
512 if (!mpd_command_list_begin(c->connection, false) ||
513 !mpd_send_delete_range(c->connection, start, end) ||
514 !mpd_send_status(c->connection) ||
515 !mpd_command_list_end(c->connection))
516 return mpdclient_handle_error(c);
518 c->events |= MPD_IDLE_PLAYLIST;
520 status = mpd_recv_status(c->connection);
521 if (status != NULL) {
522 if (c->status != NULL)
523 mpd_status_free(c->status);
524 c->status = status;
525 }
527 if (!mpd_response_finish(c->connection))
528 return mpdclient_handle_error(c);
530 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
531 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
532 /* the cheap route: match on the new playlist length
533 and its version, we can keep our local playlist
534 copy in sync */
535 c->playlist.version = mpd_status_get_queue_version(status);
537 /* remove the song from the local playlist */
538 while (end > start) {
539 --end;
541 /* remove references to the song */
542 if (c->song == playlist_get(&c->playlist, end))
543 c->song = NULL;
545 playlist_remove(&c->playlist, end);
546 }
547 }
549 return true;
550 }
552 bool
553 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
554 {
555 const struct mpd_song *song1, *song2;
556 struct mpd_status *status;
558 if (MPD_ERROR(c))
559 return false;
561 if (old_index == new_index || new_index < 0 ||
562 (guint)new_index >= c->playlist.list->len)
563 return false;
565 song1 = playlist_get(&c->playlist, old_index);
566 song2 = playlist_get(&c->playlist, new_index);
568 /* send the delete command to mpd; at the same time, get the
569 new status (to verify the playlist id) */
571 if (!mpd_command_list_begin(c->connection, false) ||
572 !mpd_send_swap_id(c->connection, mpd_song_get_id(song1),
573 mpd_song_get_id(song2)) ||
574 !mpd_send_status(c->connection) ||
575 !mpd_command_list_end(c->connection))
576 return mpdclient_handle_error(c);
578 c->events |= MPD_IDLE_PLAYLIST;
580 status = mpd_recv_status(c->connection);
581 if (status != NULL) {
582 if (c->status != NULL)
583 mpd_status_free(c->status);
584 c->status = status;
585 }
587 if (!mpd_response_finish(c->connection))
588 return mpdclient_handle_error(c);
590 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
591 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
592 /* the cheap route: match on the new playlist length
593 and its version, we can keep our local playlist
594 copy in sync */
595 c->playlist.version = mpd_status_get_queue_version(status);
597 /* swap songs in the local playlist */
598 playlist_swap(&c->playlist, old_index, new_index);
599 }
601 return true;
602 }
605 /****************************************************************************/
606 /*** Playlist management functions ******************************************/
607 /****************************************************************************/
609 /* update playlist */
610 bool
611 mpdclient_playlist_update(struct mpdclient *c)
612 {
613 struct mpd_entity *entity;
615 if (MPD_ERROR(c))
616 return false;
618 playlist_clear(&c->playlist);
620 mpd_send_list_queue_meta(c->connection);
621 while ((entity = mpd_recv_entity(c->connection))) {
622 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
623 playlist_append(&c->playlist, mpd_entity_get_song(entity));
625 mpd_entity_free(entity);
626 }
628 c->playlist.version = mpd_status_get_queue_version(c->status);
629 c->song = NULL;
631 return mpdclient_finish_command(c);
632 }
634 /* update playlist (plchanges) */
635 bool
636 mpdclient_playlist_update_changes(struct mpdclient *c)
637 {
638 struct mpd_song *song;
639 guint length;
641 if (MPD_ERROR(c))
642 return false;
644 mpd_send_queue_changes_meta(c->connection, c->playlist.version);
646 while ((song = mpd_recv_song(c->connection)) != NULL) {
647 int pos = mpd_song_get_pos(song);
649 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
650 /* update song */
651 playlist_replace(&c->playlist, pos, song);
652 } else {
653 /* add a new song */
654 playlist_append(&c->playlist, song);
655 }
657 mpd_song_free(song);
658 }
660 /* remove trailing songs */
662 length = mpd_status_get_queue_length(c->status);
663 while (length < c->playlist.list->len) {
664 guint pos = c->playlist.list->len - 1;
666 /* Remove the last playlist entry */
667 playlist_remove(&c->playlist, pos);
668 }
670 c->song = NULL;
671 c->playlist.version = mpd_status_get_queue_version(c->status);
673 return mpdclient_finish_command(c);
674 }
677 /****************************************************************************/
678 /*** Filelist functions *****************************************************/
679 /****************************************************************************/
681 static struct filelist *
682 mpdclient_recv_filelist_response(struct mpdclient *c);
684 struct filelist *
685 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
686 {
687 struct filelist *filelist;
689 if (MPD_ERROR(c))
690 return NULL;
692 mpd_send_list_meta(c->connection, path);
693 filelist = mpdclient_recv_filelist_response(c);
694 if (filelist == NULL)
695 return NULL;
697 filelist_sort_dir_play(filelist, compare_filelist_entry_path);
699 return filelist;
700 }
702 static struct filelist *
703 mpdclient_recv_filelist_response(struct mpdclient *c)
704 {
705 struct filelist *filelist;
707 filelist = filelist_new_recv(c->connection);
709 if (!mpdclient_finish_command(c)) {
710 filelist_free(filelist);
711 return NULL;
712 }
714 return filelist;
715 }
717 bool
718 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
719 {
720 guint i;
722 if (MPD_ERROR(c))
723 return false;
725 if (filelist_is_empty(fl))
726 return true;
728 mpd_command_list_begin(c->connection, false);
730 for (i = 0; i < filelist_length(fl); ++i) {
731 struct filelist_entry *entry = filelist_get(fl, i);
732 struct mpd_entity *entity = entry->entity;
734 if (entity != NULL &&
735 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
736 const struct mpd_song *song =
737 mpd_entity_get_song(entity);
739 mpd_send_add(c->connection, mpd_song_get_uri(song));
740 }
741 }
743 mpd_command_list_end(c->connection);
744 return mpdclient_finish_command(c);
745 }
747 GList *
748 mpdclient_get_artists(struct mpdclient *c)
749 {
750 GList *list = NULL;
751 struct mpd_pair *pair;
753 if (MPD_ERROR(c))
754 return NULL;
756 mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
757 mpd_search_commit(c->connection);
759 while ((pair = mpd_recv_pair_tag(c->connection,
760 MPD_TAG_ARTIST)) != NULL) {
761 list = g_list_append(list, g_strdup(pair->value));
762 mpd_return_pair(c->connection, pair);
763 }
765 if (!mpdclient_finish_command(c))
766 return string_list_free(list);
768 return list;
769 }
771 GList *
772 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
773 {
774 GList *list = NULL;
775 struct mpd_pair *pair;
777 if (MPD_ERROR(c))
778 return NULL;
780 mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
781 if (artist_utf8 != NULL)
782 mpd_search_add_tag_constraint(c->connection,
783 MPD_OPERATOR_DEFAULT,
784 MPD_TAG_ARTIST, artist_utf8);
785 mpd_search_commit(c->connection);
787 while ((pair = mpd_recv_pair_tag(c->connection,
788 MPD_TAG_ALBUM)) != 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;
797 }