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 ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
36 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
38 #define BUFSIZE 1024
40 static bool
41 MPD_ERROR(const struct mpdclient *client)
42 {
43 return client->connection == NULL ||
44 mpd_connection_get_error(client->connection) != MPD_ERROR_SUCCESS;
45 }
47 /* filelist sorting functions */
48 static gint
49 compare_filelistentry(gconstpointer filelist_entry1,
50 gconstpointer filelist_entry2)
51 {
52 const struct mpd_entity *e1, *e2;
53 int n = 0;
55 e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
56 e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
58 if (e1 != NULL && e2 != NULL &&
59 mpd_entity_get_type(e1) == mpd_entity_get_type(e2)) {
60 switch (mpd_entity_get_type(e1)) {
61 case MPD_ENTITY_TYPE_UNKNOWN:
62 break;
63 case MPD_ENTITY_TYPE_DIRECTORY:
64 n = g_utf8_collate(mpd_directory_get_path(mpd_entity_get_directory(e1)),
65 mpd_directory_get_path(mpd_entity_get_directory(e2)));
66 break;
67 case MPD_ENTITY_TYPE_SONG:
68 break;
69 case MPD_ENTITY_TYPE_PLAYLIST:
70 n = g_utf8_collate(mpd_playlist_get_path(mpd_entity_get_playlist(e1)),
71 mpd_playlist_get_path(mpd_entity_get_playlist(e2)));
72 }
73 }
74 return n;
75 }
77 /* sort by list-format */
78 gint
79 compare_filelistentry_format(gconstpointer filelist_entry1,
80 gconstpointer filelist_entry2)
81 {
82 const struct mpd_entity *e1, *e2;
83 char key1[BUFSIZE], key2[BUFSIZE];
84 int n = 0;
86 e1 = ((const struct filelist_entry *)filelist_entry1)->entity;
87 e2 = ((const struct filelist_entry *)filelist_entry2)->entity;
89 if (e1 && e2 &&
90 mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
91 mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
92 strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1));
93 strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2));
94 n = strcmp(key1,key2);
95 }
97 return n;
98 }
101 /****************************************************************************/
102 /*** mpdclient functions ****************************************************/
103 /****************************************************************************/
105 gint
106 mpdclient_handle_error(struct mpdclient *c)
107 {
108 enum mpd_error error = mpd_connection_get_error(c->connection);
110 assert(error != MPD_ERROR_SUCCESS);
112 if (error == MPD_ERROR_SERVER &&
113 mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
114 screen_auth(c))
115 return 0;
117 if (error == MPD_ERROR_SERVER)
118 error = error | (mpd_connection_get_server_error(c->connection) << 8);
120 mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
122 if (!mpd_connection_clear_error(c->connection))
123 mpdclient_disconnect(c);
125 return error;
126 }
128 static gint
129 mpdclient_finish_command(struct mpdclient *c)
130 {
131 return mpd_response_finish(c->connection)
132 ? 0 : mpdclient_handle_error(c);
133 }
135 struct mpdclient *
136 mpdclient_new(void)
137 {
138 struct mpdclient *c;
140 c = g_new0(struct mpdclient, 1);
141 playlist_init(&c->playlist);
142 c->volume = -1;
143 c->events = 0;
145 return c;
146 }
148 void
149 mpdclient_free(struct mpdclient *c)
150 {
151 mpdclient_disconnect(c);
153 mpdclient_playlist_free(&c->playlist);
155 g_free(c);
156 }
158 void
159 mpdclient_disconnect(struct mpdclient *c)
160 {
161 if (c->connection)
162 mpd_connection_free(c->connection);
163 c->connection = NULL;
165 if (c->status)
166 mpd_status_free(c->status);
167 c->status = NULL;
169 playlist_clear(&c->playlist);
171 if (c->song)
172 c->song = NULL;
173 }
175 bool
176 mpdclient_connect(struct mpdclient *c,
177 const gchar *host,
178 gint port,
179 gfloat _timeout,
180 const gchar *password)
181 {
182 /* close any open connection */
183 if( c->connection )
184 mpdclient_disconnect(c);
186 /* connect to MPD */
187 c->connection = mpd_connection_new(host, port, _timeout * 1000);
188 if (c->connection == NULL)
189 g_error("Out of memory");
191 if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
192 mpdclient_handle_error(c);
193 mpdclient_disconnect(c);
194 return false;
195 }
197 /* send password */
198 if (password != NULL && !mpd_run_password(c->connection, password)) {
199 mpdclient_handle_error(c);
200 mpdclient_disconnect(c);
201 return false;
202 }
204 return true;
205 }
207 bool
208 mpdclient_update(struct mpdclient *c)
209 {
210 bool retval;
212 c->volume = -1;
214 if (MPD_ERROR(c))
215 return false;
217 /* always announce these options as long as we don't have real
218 "idle" support */
219 c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
221 /* free the old status */
222 if (c->status)
223 mpd_status_free(c->status);
225 /* retrieve new status */
226 c->status = mpd_run_status(c->connection);
227 if (c->status == NULL)
228 return mpdclient_handle_error(c) == 0;
230 if (c->update_id != mpd_status_get_update_id(c->status)) {
231 c->events |= MPD_IDLE_UPDATE;
233 if (c->update_id > 0)
234 c->events |= MPD_IDLE_DATABASE;
235 }
237 c->update_id = mpd_status_get_update_id(c->status);
239 if (c->volume != mpd_status_get_volume(c->status))
240 c->events |= MPD_IDLE_MIXER;
242 c->volume = mpd_status_get_volume(c->status);
244 /* check if the playlist needs an update */
245 if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
246 c->events |= MPD_IDLE_PLAYLIST;
248 if (!playlist_is_empty(&c->playlist))
249 retval = mpdclient_playlist_update_changes(c);
250 else
251 retval = mpdclient_playlist_update(c);
252 } else
253 retval = true;
255 /* update the current song */
256 if (!c->song || mpd_status_get_song_id(c->status)) {
257 c->song = playlist_get_song(&c->playlist,
258 mpd_status_get_song_pos(c->status));
259 }
261 return retval;
262 }
265 /****************************************************************************/
266 /*** MPD Commands **********************************************************/
267 /****************************************************************************/
269 gint
270 mpdclient_cmd_play(struct mpdclient *c, gint idx)
271 {
272 const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
274 if (MPD_ERROR(c))
275 return -1;
277 if (song)
278 mpd_send_play_id(c->connection, mpd_song_get_id(song));
279 else
280 mpd_send_play(c->connection);
282 return mpdclient_finish_command(c);
283 }
285 gint
286 mpdclient_cmd_crop(struct mpdclient *c)
287 {
288 struct mpd_status *status;
289 bool playing;
290 int length, current;
292 if (MPD_ERROR(c))
293 return -1;
295 status = mpd_run_status(c->connection);
296 if (status == NULL)
297 return mpdclient_handle_error(c);
299 playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
300 mpd_status_get_state(status) == MPD_STATE_PAUSE;
301 length = mpd_status_get_queue_length(status);
302 current = mpd_status_get_song_pos(status);
304 mpd_status_free(status);
306 if (!playing || length < 2)
307 return 0;
309 mpd_command_list_begin(c->connection, false);
311 while (--length >= 0)
312 if (length != current)
313 mpd_send_delete(c->connection, length);
315 mpd_command_list_end(c->connection);
317 return mpdclient_finish_command(c);
318 }
320 gint
321 mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end)
322 {
323 mpd_send_shuffle_range(c->connection, start, end);
324 return mpdclient_finish_command(c);
325 }
327 gint
328 mpdclient_cmd_clear(struct mpdclient *c)
329 {
330 gint retval = 0;
332 if (MPD_ERROR(c))
333 return -1;
335 mpd_send_clear(c->connection);
336 retval = mpdclient_finish_command(c);
338 if (retval)
339 c->events |= MPD_IDLE_PLAYLIST;
341 return retval;
342 }
344 gint
345 mpdclient_cmd_volume(struct mpdclient *c, gint value)
346 {
347 if (MPD_ERROR(c))
348 return -1;
350 mpd_send_set_volume(c->connection, value);
351 return mpdclient_finish_command(c);
352 }
354 gint mpdclient_cmd_volume_up(struct mpdclient *c)
355 {
356 if (MPD_ERROR(c))
357 return -1;
359 if (c->status == NULL ||
360 mpd_status_get_volume(c->status) == -1)
361 return 0;
363 if (c->volume < 0)
364 c->volume = mpd_status_get_volume(c->status);
366 if (c->volume >= 100)
367 return 0;
369 return mpdclient_cmd_volume(c, ++c->volume);
370 }
372 gint mpdclient_cmd_volume_down(struct mpdclient *c)
373 {
374 if (MPD_ERROR(c))
375 return -1;
377 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
378 return 0;
380 if (c->volume < 0)
381 c->volume = mpd_status_get_volume(c->status);
383 if (c->volume <= 0)
384 return 0;
386 return mpdclient_cmd_volume(c, --c->volume);
387 }
389 gint
390 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
391 {
392 if (MPD_ERROR(c))
393 return -1;
395 mpd_send_add(c->connection, path_utf8);
396 return mpdclient_finish_command(c);
397 }
399 gint
400 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
401 {
402 struct mpd_status *status;
403 struct mpd_song *new_song;
405 assert(c != NULL);
406 assert(song != NULL);
408 if (MPD_ERROR(c) || c->status == NULL)
409 return -1;
411 /* send the add command to mpd; at the same time, get the new
412 status (to verify the new playlist id) and the last song
413 (we hope that's the song we just added) */
415 if (!mpd_command_list_begin(c->connection, true) ||
416 !mpd_send_add(c->connection, mpd_song_get_uri(song)) ||
417 !mpd_send_status(c->connection) ||
418 !mpd_send_get_queue_song_pos(c->connection,
419 playlist_length(&c->playlist)) ||
420 !mpd_command_list_end(c->connection) ||
421 !mpd_response_next(c->connection))
422 return mpdclient_handle_error(c);
424 c->events |= MPD_IDLE_PLAYLIST;
426 status = mpd_recv_status(c->connection);
427 if (status != NULL) {
428 if (c->status != NULL)
429 mpd_status_free(c->status);
430 c->status = status;
431 }
433 if (!mpd_response_next(c->connection))
434 return mpdclient_handle_error(c);
436 new_song = mpd_recv_song(c->connection);
437 if (!mpd_response_finish(c->connection) || new_song == NULL) {
438 if (new_song != NULL)
439 mpd_song_free(new_song);
441 return mpd_connection_clear_error(c->connection)
442 ? 0 : mpdclient_handle_error(c);
443 }
445 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
446 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
447 /* the cheap route: match on the new playlist length
448 and its version, we can keep our local playlist
449 copy in sync */
450 c->playlist.version = mpd_status_get_queue_version(status);
452 /* the song we just received has the correct id;
453 append it to the local playlist */
454 playlist_append(&c->playlist, new_song);
455 }
457 mpd_song_free(new_song);
459 return -0;
460 }
462 gint
463 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
464 {
465 gint retval = 0;
466 struct mpd_song *song;
468 if (MPD_ERROR(c))
469 return -1;
471 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
472 return -1;
474 song = playlist_get(&c->playlist, idx);
476 /* send the delete command to mpd */
477 mpd_send_delete_id(c->connection, mpd_song_get_id(song));
478 if( (retval=mpdclient_finish_command(c)) )
479 return retval;
481 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
482 /* increment the playlist id, so we don't retrieve a new playlist */
483 c->playlist.version++;
485 /* remove the song from the playlist */
486 playlist_remove_reuse(&c->playlist, idx);
488 c->events |= MPD_IDLE_PLAYLIST;
490 /* remove references to the song */
491 if (c->song == song)
492 c->song = NULL;
494 mpd_song_free(song);
495 #endif
497 return 0;
498 }
500 gint
501 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
502 {
503 gint n;
504 struct mpd_song *song1, *song2;
506 if (MPD_ERROR(c))
507 return -1;
509 if (old_index == new_index || new_index < 0 ||
510 (guint)new_index >= c->playlist.list->len)
511 return -1;
513 song1 = playlist_get(&c->playlist, old_index);
514 song2 = playlist_get(&c->playlist, new_index);
516 /* send the move command to mpd */
517 mpd_send_swap_id(c->connection,
518 mpd_song_get_id(song1), mpd_song_get_id(song2));
519 if( (n=mpdclient_finish_command(c)) )
520 return n;
522 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
523 /* update the playlist */
524 playlist_swap(&c->playlist, old_index, new_index);
526 /* increment the playlist id, so we don't retrieve a new playlist */
527 c->playlist.version++;
528 #endif
530 c->events |= MPD_IDLE_PLAYLIST;
532 return 0;
533 }
535 gint
536 mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8)
537 {
538 gint retval = 0;
540 if (MPD_ERROR(c))
541 return -1;
543 mpd_send_save(c->connection, filename_utf8);
544 if ((retval = mpdclient_finish_command(c)) == 0) {
545 c->events |= MPD_IDLE_STORED_PLAYLIST;
546 }
548 return retval;
549 }
551 gint
552 mpdclient_cmd_load_playlist(struct mpdclient *c, const gchar *filename_utf8)
553 {
554 if (MPD_ERROR(c))
555 return -1;
557 mpd_send_load(c->connection, filename_utf8);
558 return mpdclient_finish_command(c);
559 }
561 gint
562 mpdclient_cmd_delete_playlist(struct mpdclient *c, const gchar *filename_utf8)
563 {
564 gint retval = 0;
566 if (MPD_ERROR(c))
567 return -1;
569 mpd_send_rm(c->connection, filename_utf8);
570 if ((retval = mpdclient_finish_command(c)) == 0)
571 c->events |= MPD_IDLE_STORED_PLAYLIST;
573 return retval;
574 }
577 /****************************************************************************/
578 /*** Playlist management functions ******************************************/
579 /****************************************************************************/
581 /* update playlist */
582 bool
583 mpdclient_playlist_update(struct mpdclient *c)
584 {
585 struct mpd_entity *entity;
587 if (MPD_ERROR(c))
588 return false;
590 playlist_clear(&c->playlist);
592 mpd_send_list_queue_meta(c->connection);
593 while ((entity = mpd_recv_entity(c->connection))) {
594 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
595 playlist_append(&c->playlist, mpd_entity_get_song(entity));
597 mpd_entity_free(entity);
598 }
600 c->playlist.version = mpd_status_get_queue_version(c->status);
601 c->song = NULL;
603 return mpdclient_finish_command(c) == 0;
604 }
606 /* update playlist (plchanges) */
607 bool
608 mpdclient_playlist_update_changes(struct mpdclient *c)
609 {
610 struct mpd_song *song;
611 guint length;
613 if (MPD_ERROR(c))
614 return false;
616 mpd_send_queue_changes_meta(c->connection, c->playlist.version);
618 while ((song = mpd_recv_song(c->connection)) != NULL) {
619 int pos = mpd_song_get_pos(song);
621 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
622 /* update song */
623 playlist_replace(&c->playlist, pos, song);
624 } else {
625 /* add a new song */
626 playlist_append(&c->playlist, song);
627 }
629 mpd_song_free(song);
630 }
632 /* remove trailing songs */
634 length = mpd_status_get_queue_length(c->status);
635 while (length < c->playlist.list->len) {
636 guint pos = c->playlist.list->len - 1;
638 /* Remove the last playlist entry */
639 playlist_remove(&c->playlist, pos);
640 }
642 c->song = NULL;
643 c->playlist.version = mpd_status_get_queue_version(c->status);
645 return mpdclient_finish_command(c) == 0;
646 }
649 /****************************************************************************/
650 /*** Filelist functions *****************************************************/
651 /****************************************************************************/
653 struct filelist *
654 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
655 {
656 struct filelist *filelist;
657 struct mpd_entity *entity;
659 if (MPD_ERROR(c))
660 return NULL;
662 mpd_send_list_meta(c->connection, path);
663 filelist = filelist_new();
665 while ((entity = mpd_recv_entity(c->connection)) != NULL)
666 filelist_append(filelist, entity);
668 if (mpdclient_finish_command(c)) {
669 filelist_free(filelist);
670 return NULL;
671 }
673 filelist_sort_dir_play(filelist, compare_filelistentry);
675 return filelist;
676 }
678 static struct filelist *
679 mpdclient_recv_filelist_response(struct mpdclient *c)
680 {
681 struct filelist *filelist;
682 struct mpd_entity *entity;
684 filelist = filelist_new();
686 while ((entity = mpd_recv_entity(c->connection)) != NULL)
687 filelist_append(filelist, entity);
689 if (mpdclient_finish_command(c)) {
690 filelist_free(filelist);
691 return NULL;
692 }
694 return filelist;
695 }
697 struct filelist *
698 mpdclient_filelist_search(struct mpdclient *c,
699 int exact_match,
700 enum mpd_tag_type tag,
701 gchar *filter_utf8)
702 {
703 if (MPD_ERROR(c))
704 return NULL;
706 mpd_search_db_songs(c->connection, exact_match);
707 mpd_search_add_tag_constraint(c->connection, MPD_OPERATOR_DEFAULT,
708 tag, filter_utf8);
709 mpd_search_commit(c->connection);
711 return mpdclient_recv_filelist_response(c);
712 }
714 int
715 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
716 {
717 guint i;
719 if (MPD_ERROR(c))
720 return -1;
722 if (filelist_is_empty(fl))
723 return 0;
725 mpd_command_list_begin(c->connection, false);
727 for (i = 0; i < filelist_length(fl); ++i) {
728 struct filelist_entry *entry = filelist_get(fl, i);
729 struct mpd_entity *entity = entry->entity;
731 if (entity != NULL &&
732 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
733 const struct mpd_song *song =
734 mpd_entity_get_song(entity);
735 const char *uri = mpd_song_get_uri(song);
737 if (uri != NULL)
738 mpd_send_add(c->connection, uri);
739 }
740 }
742 mpd_command_list_end(c->connection);
743 return mpdclient_finish_command(c);
744 }
746 GList *
747 mpdclient_get_artists(struct mpdclient *c)
748 {
749 GList *list = NULL;
750 struct mpd_pair *pair;
752 if (MPD_ERROR(c))
753 return NULL;
755 mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
756 mpd_search_commit(c->connection);
758 while ((pair = mpd_recv_pair_tag(c->connection,
759 MPD_TAG_ARTIST)) != NULL) {
760 list = g_list_append(list, g_strdup(pair->value));
761 mpd_return_pair(c->connection, pair);
762 }
764 if (mpdclient_finish_command(c))
765 return string_list_free(list);
767 return list;
768 }
770 GList *
771 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
772 {
773 GList *list = NULL;
774 struct mpd_pair *pair;
776 if (MPD_ERROR(c))
777 return NULL;
779 mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
780 if (artist_utf8 != NULL)
781 mpd_search_add_tag_constraint(c->connection,
782 MPD_OPERATOR_DEFAULT,
783 MPD_TAG_ARTIST, artist_utf8);
784 mpd_search_commit(c->connection);
786 while ((pair = mpd_recv_pair_tag(c->connection,
787 MPD_TAG_ALBUM)) != NULL) {
788 list = g_list_append(list, g_strdup(pair->value));
789 mpd_return_pair(c->connection, pair);
790 }
792 if (mpdclient_finish_command(c))
793 return string_list_free(list);
795 return list;
796 }