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_free(c);
157 }
159 void
160 mpdclient_disconnect(struct mpdclient *c)
161 {
162 if (c->connection)
163 mpd_connection_free(c->connection);
164 c->connection = NULL;
166 if (c->status)
167 mpd_status_free(c->status);
168 c->status = NULL;
170 playlist_clear(&c->playlist);
172 if (c->song)
173 c->song = NULL;
174 }
176 bool
177 mpdclient_connect(struct mpdclient *c,
178 const gchar *host,
179 gint port,
180 gfloat _timeout,
181 const gchar *password)
182 {
183 /* close any open connection */
184 if( c->connection )
185 mpdclient_disconnect(c);
187 /* connect to MPD */
188 c->connection = mpd_connection_new(host, port, _timeout * 1000);
189 if (c->connection == NULL)
190 g_error("Out of memory");
192 if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
193 mpdclient_handle_error(c);
194 mpdclient_disconnect(c);
195 return false;
196 }
198 /* send password */
199 if (password != NULL && !mpd_run_password(c->connection, password)) {
200 mpdclient_handle_error(c);
201 mpdclient_disconnect(c);
202 return false;
203 }
205 return true;
206 }
208 bool
209 mpdclient_update(struct mpdclient *c)
210 {
211 bool retval;
213 c->volume = -1;
215 if (MPD_ERROR(c))
216 return false;
218 /* always announce these options as long as we don't have real
219 "idle" support */
220 c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
222 /* free the old status */
223 if (c->status)
224 mpd_status_free(c->status);
226 /* retrieve new status */
227 c->status = mpd_run_status(c->connection);
228 if (c->status == NULL)
229 return mpdclient_handle_error(c) == 0;
231 if (c->update_id != mpd_status_get_update_id(c->status)) {
232 c->events |= MPD_IDLE_UPDATE;
234 if (c->update_id > 0)
235 c->events |= MPD_IDLE_DATABASE;
236 }
238 c->update_id = mpd_status_get_update_id(c->status);
240 if (c->volume != mpd_status_get_volume(c->status))
241 c->events |= MPD_IDLE_MIXER;
243 c->volume = mpd_status_get_volume(c->status);
245 /* check if the playlist needs an update */
246 if (c->playlist.id != mpd_status_get_queue_version(c->status)) {
247 c->events |= MPD_IDLE_PLAYLIST;
249 if (!playlist_is_empty(&c->playlist))
250 retval = mpdclient_playlist_update_changes(c);
251 else
252 retval = mpdclient_playlist_update(c);
253 } else
254 retval = true;
256 /* update the current song */
257 if (!c->song || mpd_status_get_song_id(c->status)) {
258 c->song = playlist_get_song(&c->playlist,
259 mpd_status_get_song_pos(c->status));
260 }
262 return retval;
263 }
266 /****************************************************************************/
267 /*** MPD Commands **********************************************************/
268 /****************************************************************************/
270 gint
271 mpdclient_cmd_play(struct mpdclient *c, gint idx)
272 {
273 const struct mpd_song *song = playlist_get_song(&c->playlist, idx);
275 if (MPD_ERROR(c))
276 return -1;
278 if (song)
279 mpd_send_play_id(c->connection, mpd_song_get_id(song));
280 else
281 mpd_send_play(c->connection);
283 return mpdclient_finish_command(c);
284 }
286 gint
287 mpdclient_cmd_crop(struct mpdclient *c)
288 {
289 struct mpd_status *status;
290 bool playing;
291 int length, current;
293 if (MPD_ERROR(c))
294 return -1;
296 status = mpd_run_status(c->connection);
297 if (status == NULL)
298 return mpdclient_handle_error(c);
300 playing = mpd_status_get_state(status) == MPD_STATE_PLAY ||
301 mpd_status_get_state(status) == MPD_STATE_PAUSE;
302 length = mpd_status_get_queue_length(status);
303 current = mpd_status_get_song_pos(status);
305 mpd_status_free(status);
307 if (!playing || length < 2)
308 return 0;
310 mpd_command_list_begin(c->connection, false);
312 while (--length >= 0)
313 if (length != current)
314 mpd_send_delete(c->connection, length);
316 mpd_command_list_end(c->connection);
318 return mpdclient_finish_command(c);
319 }
321 gint
322 mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end)
323 {
324 mpd_send_shuffle_range(c->connection, start, end);
325 return mpdclient_finish_command(c);
326 }
328 gint
329 mpdclient_cmd_clear(struct mpdclient *c)
330 {
331 gint retval = 0;
333 if (MPD_ERROR(c))
334 return -1;
336 mpd_send_clear(c->connection);
337 retval = mpdclient_finish_command(c);
339 if (retval)
340 c->events |= MPD_IDLE_PLAYLIST;
342 return retval;
343 }
345 gint
346 mpdclient_cmd_volume(struct mpdclient *c, gint value)
347 {
348 if (MPD_ERROR(c))
349 return -1;
351 mpd_send_set_volume(c->connection, value);
352 return mpdclient_finish_command(c);
353 }
355 gint mpdclient_cmd_volume_up(struct mpdclient *c)
356 {
357 if (MPD_ERROR(c))
358 return -1;
360 if (c->status == NULL ||
361 mpd_status_get_volume(c->status) == -1)
362 return 0;
364 if (c->volume < 0)
365 c->volume = mpd_status_get_volume(c->status);
367 if (c->volume >= 100)
368 return 0;
370 return mpdclient_cmd_volume(c, ++c->volume);
371 }
373 gint mpdclient_cmd_volume_down(struct mpdclient *c)
374 {
375 if (MPD_ERROR(c))
376 return -1;
378 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
379 return 0;
381 if (c->volume < 0)
382 c->volume = mpd_status_get_volume(c->status);
384 if (c->volume <= 0)
385 return 0;
387 return mpdclient_cmd_volume(c, --c->volume);
388 }
390 gint
391 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
392 {
393 if (MPD_ERROR(c))
394 return -1;
396 mpd_send_add(c->connection, path_utf8);
397 return mpdclient_finish_command(c);
398 }
400 gint
401 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
402 {
403 gint retval = 0;
405 if (MPD_ERROR(c))
406 return -1;
408 if (song == NULL)
409 return -1;
411 /* send the add command to mpd */
412 mpd_send_add(c->connection, mpd_song_get_uri(song));
413 if( (retval=mpdclient_finish_command(c)) )
414 return retval;
416 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
417 /* add the song to playlist */
418 playlist_append(&c->playlist, song);
420 /* increment the playlist id, so we don't retrieve a new playlist */
421 c->playlist.id++;
423 c->events |= MPD_IDLE_PLAYLIST;
424 #endif
426 return 0;
427 }
429 gint
430 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
431 {
432 gint retval = 0;
433 struct mpd_song *song;
435 if (MPD_ERROR(c))
436 return -1;
438 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
439 return -1;
441 song = playlist_get(&c->playlist, idx);
443 /* send the delete command to mpd */
444 mpd_send_delete_id(c->connection, mpd_song_get_id(song));
445 if( (retval=mpdclient_finish_command(c)) )
446 return retval;
448 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
449 /* increment the playlist id, so we don't retrieve a new playlist */
450 c->playlist.id++;
452 /* remove the song from the playlist */
453 playlist_remove_reuse(&c->playlist, idx);
455 c->events |= MPD_IDLE_PLAYLIST;
457 /* remove references to the song */
458 if (c->song == song)
459 c->song = NULL;
461 mpd_song_free(song);
462 #endif
464 return 0;
465 }
467 gint
468 mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
469 {
470 gint n;
471 struct mpd_song *song1, *song2;
473 if (MPD_ERROR(c))
474 return -1;
476 if (old_index == new_index || new_index < 0 ||
477 (guint)new_index >= c->playlist.list->len)
478 return -1;
480 song1 = playlist_get(&c->playlist, old_index);
481 song2 = playlist_get(&c->playlist, new_index);
483 /* send the move command to mpd */
484 mpd_send_swap_id(c->connection,
485 mpd_song_get_id(song1), mpd_song_get_id(song2));
486 if( (n=mpdclient_finish_command(c)) )
487 return n;
489 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
490 /* update the playlist */
491 playlist_swap(&c->playlist, old_index, new_index);
493 /* increment the playlist id, so we don't retrieve a new playlist */
494 c->playlist.id++;
495 #endif
497 c->events |= MPD_IDLE_PLAYLIST;
499 return 0;
500 }
502 gint
503 mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8)
504 {
505 gint retval = 0;
507 if (MPD_ERROR(c))
508 return -1;
510 mpd_send_save(c->connection, filename_utf8);
511 if ((retval = mpdclient_finish_command(c)) == 0) {
512 c->events |= MPD_IDLE_STORED_PLAYLIST;
513 }
515 return retval;
516 }
518 gint
519 mpdclient_cmd_load_playlist(struct mpdclient *c, const gchar *filename_utf8)
520 {
521 if (MPD_ERROR(c))
522 return -1;
524 mpd_send_load(c->connection, filename_utf8);
525 return mpdclient_finish_command(c);
526 }
528 gint
529 mpdclient_cmd_delete_playlist(struct mpdclient *c, const gchar *filename_utf8)
530 {
531 gint retval = 0;
533 if (MPD_ERROR(c))
534 return -1;
536 mpd_send_rm(c->connection, filename_utf8);
537 if ((retval = mpdclient_finish_command(c)) == 0)
538 c->events |= MPD_IDLE_STORED_PLAYLIST;
540 return retval;
541 }
544 /****************************************************************************/
545 /*** Playlist management functions ******************************************/
546 /****************************************************************************/
548 /* update playlist */
549 bool
550 mpdclient_playlist_update(struct mpdclient *c)
551 {
552 struct mpd_entity *entity;
554 if (MPD_ERROR(c))
555 return false;
557 playlist_clear(&c->playlist);
559 mpd_send_list_queue_meta(c->connection);
560 while ((entity = mpd_recv_entity(c->connection))) {
561 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
562 playlist_append(&c->playlist, mpd_entity_get_song(entity));
564 mpd_entity_free(entity);
565 }
567 c->playlist.id = mpd_status_get_queue_version(c->status);
568 c->song = NULL;
570 return mpdclient_finish_command(c) == 0;
571 }
573 /* update playlist (plchanges) */
574 bool
575 mpdclient_playlist_update_changes(struct mpdclient *c)
576 {
577 struct mpd_song *song;
578 guint length;
580 if (MPD_ERROR(c))
581 return false;
583 mpd_send_queue_changes_meta(c->connection, c->playlist.id);
585 while ((song = mpd_recv_song(c->connection)) != NULL) {
586 int pos = mpd_song_get_pos(song);
588 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
589 /* update song */
590 playlist_replace(&c->playlist, pos, song);
591 } else {
592 /* add a new song */
593 playlist_append(&c->playlist, song);
594 }
596 mpd_song_free(song);
597 }
599 /* remove trailing songs */
601 length = mpd_status_get_queue_length(c->status);
602 while (length < c->playlist.list->len) {
603 guint pos = c->playlist.list->len - 1;
605 /* Remove the last playlist entry */
606 playlist_remove(&c->playlist, pos);
607 }
609 c->song = NULL;
610 c->playlist.id = mpd_status_get_queue_version(c->status);
612 return mpdclient_finish_command(c) == 0;
613 }
616 /****************************************************************************/
617 /*** Filelist functions *****************************************************/
618 /****************************************************************************/
620 struct filelist *
621 mpdclient_filelist_get(struct mpdclient *c, const gchar *path)
622 {
623 struct filelist *filelist;
624 struct mpd_entity *entity;
626 if (MPD_ERROR(c))
627 return NULL;
629 mpd_send_list_meta(c->connection, path);
630 filelist = filelist_new();
632 while ((entity = mpd_recv_entity(c->connection)) != NULL)
633 filelist_append(filelist, entity);
635 if (mpdclient_finish_command(c)) {
636 filelist_free(filelist);
637 return NULL;
638 }
640 filelist_sort_dir_play(filelist, compare_filelistentry);
642 return filelist;
643 }
645 static struct filelist *
646 mpdclient_recv_filelist_response(struct mpdclient *c)
647 {
648 struct filelist *filelist;
649 struct mpd_entity *entity;
651 filelist = filelist_new();
653 while ((entity = mpd_recv_entity(c->connection)) != NULL)
654 filelist_append(filelist, entity);
656 if (mpdclient_finish_command(c)) {
657 filelist_free(filelist);
658 return NULL;
659 }
661 return filelist;
662 }
664 struct filelist *
665 mpdclient_filelist_search(struct mpdclient *c,
666 int exact_match,
667 enum mpd_tag_type tag,
668 gchar *filter_utf8)
669 {
670 if (MPD_ERROR(c))
671 return NULL;
673 mpd_search_db_songs(c->connection, exact_match);
674 mpd_search_add_tag_constraint(c->connection, MPD_OPERATOR_DEFAULT,
675 tag, filter_utf8);
676 mpd_search_commit(c->connection);
678 return mpdclient_recv_filelist_response(c);
679 }
681 int
682 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
683 {
684 guint i;
686 if (MPD_ERROR(c))
687 return -1;
689 if (filelist_is_empty(fl))
690 return 0;
692 mpd_command_list_begin(c->connection, false);
694 for (i = 0; i < filelist_length(fl); ++i) {
695 struct filelist_entry *entry = filelist_get(fl, i);
696 struct mpd_entity *entity = entry->entity;
698 if (entity != NULL &&
699 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
700 const struct mpd_song *song =
701 mpd_entity_get_song(entity);
702 const char *uri = mpd_song_get_uri(song);
704 if (uri != NULL)
705 mpd_send_add(c->connection, uri);
706 }
707 }
709 mpd_command_list_end(c->connection);
710 return mpdclient_finish_command(c);
711 }
713 GList *
714 mpdclient_get_artists(struct mpdclient *c)
715 {
716 GList *list = NULL;
717 struct mpd_pair *pair;
719 if (MPD_ERROR(c))
720 return NULL;
722 mpd_search_db_tags(c->connection, MPD_TAG_ARTIST);
723 mpd_search_commit(c->connection);
725 while ((pair = mpd_recv_pair_tag(c->connection,
726 MPD_TAG_ARTIST)) != NULL) {
727 list = g_list_append(list, g_strdup(pair->value));
728 mpd_return_pair(c->connection, pair);
729 }
731 if (mpdclient_finish_command(c))
732 return string_list_free(list);
734 return list;
735 }
737 GList *
738 mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8)
739 {
740 GList *list = NULL;
741 struct mpd_pair *pair;
743 if (MPD_ERROR(c))
744 return NULL;
746 mpd_search_db_tags(c->connection, MPD_TAG_ALBUM);
747 if (artist_utf8 != NULL)
748 mpd_search_add_tag_constraint(c->connection,
749 MPD_OPERATOR_DEFAULT,
750 MPD_TAG_ARTIST, artist_utf8);
751 mpd_search_commit(c->connection);
753 while ((pair = mpd_recv_pair_tag(c->connection,
754 MPD_TAG_ALBUM)) != NULL) {
755 list = g_list_append(list, g_strdup(pair->value));
756 mpd_return_pair(c->connection, pair);
757 }
759 if (mpdclient_finish_command(c))
760 return string_list_free(list);
762 return list;
763 }