1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2010 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.
20 #include "mpdclient.h"
22 #include "screen_client.h"
29 #include <mpd/client.h>
38 /* sort by list-format */
40 compare_filelistentry_format(gconstpointer filelist_entry1
,
41 gconstpointer filelist_entry2
)
43 const struct mpd_entity
*e1
, *e2
;
44 char key1
[BUFSIZE
], key2
[BUFSIZE
];
47 e1
= ((const struct filelist_entry
*)filelist_entry1
)->entity
;
48 e2
= ((const struct filelist_entry
*)filelist_entry2
)->entity
;
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
);
62 /****************************************************************************/
63 /*** mpdclient functions ****************************************************/
64 /****************************************************************************/
67 mpdclient_handle_error(struct mpdclient
*c
)
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
&&
78 mpdclient_ui_error(mpd_connection_get_error_message(c
->connection
));
80 if (!mpd_connection_clear_error(c
->connection
))
81 mpdclient_disconnect(c
);
91 c
= g_new0(struct mpdclient
, 1);
92 playlist_init(&c
->playlist
);
100 mpdclient_free(struct mpdclient
*c
)
102 mpdclient_disconnect(c
);
104 mpdclient_playlist_free(&c
->playlist
);
110 mpdclient_disconnect(struct mpdclient
*c
)
112 if (c
->source
!= NULL
) {
113 mpd_glib_free(c
->source
);
119 mpd_connection_free(c
->connection
);
120 c
->connection
= NULL
;
123 mpd_status_free(c
->status
);
126 playlist_clear(&c
->playlist
);
131 /* everything has changed after a disconnect */
132 c
->events
|= MPD_IDLE_ALL
;
136 mpdclient_connect(struct mpdclient
*c
,
140 const gchar
*password
)
142 /* close any open connection */
144 mpdclient_disconnect(c
);
147 c
->connection
= mpd_connection_new(host
, port
, timeout_ms
);
148 if (c
->connection
== NULL
)
149 g_error("Out of memory");
151 if (mpd_connection_get_error(c
->connection
) != MPD_ERROR_SUCCESS
) {
152 mpdclient_handle_error(c
);
153 mpdclient_disconnect(c
);
158 if (password
!= NULL
&& !mpd_run_password(c
->connection
, password
)) {
159 mpdclient_handle_error(c
);
160 mpdclient_disconnect(c
);
168 mpdclient_update(struct mpdclient
*c
)
170 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
174 if (connection
== NULL
)
177 /* always announce these options as long as we don't have
179 if (c
->source
== NULL
)
180 c
->events
|= MPD_IDLE_PLAYER
|MPD_IDLE_OPTIONS
;
182 /* free the old status */
184 mpd_status_free(c
->status
);
186 /* retrieve new status */
187 c
->status
= mpd_run_status(connection
);
188 if (c
->status
== NULL
)
189 return mpdclient_handle_error(c
);
191 if (c
->source
== NULL
&&
192 c
->update_id
!= mpd_status_get_update_id(c
->status
)) {
193 c
->events
|= MPD_IDLE_UPDATE
;
195 if (c
->update_id
> 0)
196 c
->events
|= MPD_IDLE_DATABASE
;
199 c
->update_id
= mpd_status_get_update_id(c
->status
);
201 if (c
->source
== NULL
&&
202 c
->volume
!= mpd_status_get_volume(c
->status
))
203 c
->events
|= MPD_IDLE_MIXER
;
205 c
->volume
= mpd_status_get_volume(c
->status
);
207 /* check if the playlist needs an update */
208 if (c
->playlist
.version
!= mpd_status_get_queue_version(c
->status
)) {
211 if (c
->source
== NULL
)
212 c
->events
|= MPD_IDLE_QUEUE
;
214 if (!playlist_is_empty(&c
->playlist
))
215 retval
= mpdclient_playlist_update_changes(c
);
217 retval
= mpdclient_playlist_update(c
);
222 /* update the current song */
223 if (!c
->song
|| mpd_status_get_song_id(c
->status
) >= 0) {
224 c
->song
= playlist_get_song(&c
->playlist
,
225 mpd_status_get_song_pos(c
->status
));
231 struct mpd_connection
*
232 mpdclient_get_connection(struct mpdclient
*c
)
234 if (c
->source
!= NULL
&& c
->idle
) {
236 mpd_glib_leave(c
->source
);
239 return c
->connection
;
243 mpdclient_put_connection(struct mpdclient
*c
)
245 assert(c
->source
== NULL
|| c
->connection
!= NULL
);
247 if (c
->source
!= NULL
&& !c
->idle
) {
248 c
->idle
= mpd_glib_enter(c
->source
);
252 static struct mpd_status
*
253 mpdclient_recv_status(struct mpdclient
*c
)
255 struct mpd_status
*status
;
257 assert(c
->connection
!= NULL
);
259 status
= mpd_recv_status(c
->connection
);
260 if (status
== NULL
) {
261 mpdclient_handle_error(c
);
265 if (c
->status
!= NULL
)
266 mpd_status_free(c
->status
);
267 return c
->status
= status
;
270 /****************************************************************************/
271 /*** MPD Commands **********************************************************/
272 /****************************************************************************/
275 mpdclient_cmd_crop(struct mpdclient
*c
)
277 struct mpd_connection
*connection
;
280 if (!mpdclient_is_playing(c
))
283 length
= mpd_status_get_queue_length(c
->status
);
284 current
= mpd_status_get_song_pos(c
->status
);
285 if (current
< 0 || mpd_status_get_queue_length(c
->status
) < 2)
288 connection
= mpdclient_get_connection(c
);
289 if (connection
== NULL
)
292 mpd_command_list_begin(connection
, false);
294 if (mpd_connection_cmp_server_version(connection
, 0, 16, 0) >= 0) {
295 if (current
< length
- 1)
296 mpd_send_delete_range(connection
, current
+ 1, length
);
298 mpd_send_delete_range(connection
, 0, current
);
300 while (--length
>= 0)
301 if (length
!= current
)
302 mpd_send_delete(connection
, length
);
304 mpd_command_list_end(connection
);
306 return mpdclient_finish_command(c
);
310 mpdclient_cmd_clear(struct mpdclient
*c
)
312 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
313 struct mpd_status
*status
;
315 if (connection
== NULL
)
318 /* send "clear" and "status" */
319 if (!mpd_command_list_begin(connection
, false) ||
320 !mpd_send_clear(connection
) ||
321 !mpd_send_status(connection
) ||
322 !mpd_command_list_end(connection
))
323 return mpdclient_handle_error(c
);
325 /* receive the new status, store it in the mpdclient struct */
327 status
= mpdclient_recv_status(c
);
331 if (!mpd_response_finish(connection
))
332 return mpdclient_handle_error(c
);
334 /* update mpdclient.playlist */
336 if (mpd_status_get_queue_length(status
) == 0) {
337 /* after the "clear" command, the queue is really
338 empty - this means we can clear it locally,
339 reducing the UI latency */
340 playlist_clear(&c
->playlist
);
341 c
->playlist
.version
= mpd_status_get_queue_version(status
);
345 c
->events
|= MPD_IDLE_QUEUE
;
350 mpdclient_cmd_volume(struct mpdclient
*c
, gint value
)
352 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
353 if (connection
== NULL
)
356 mpd_send_set_volume(connection
, value
);
357 return mpdclient_finish_command(c
);
361 mpdclient_cmd_volume_up(struct mpdclient
*c
)
363 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
364 if (connection
== NULL
)
367 if (c
->status
== NULL
||
368 mpd_status_get_volume(c
->status
) == -1)
372 c
->volume
= mpd_status_get_volume(c
->status
);
374 if (c
->volume
>= 100)
377 return mpdclient_cmd_volume(c
, ++c
->volume
);
381 mpdclient_cmd_volume_down(struct mpdclient
*c
)
383 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
384 if (connection
== NULL
)
387 if (c
->status
== NULL
|| mpd_status_get_volume(c
->status
) < 0)
391 c
->volume
= mpd_status_get_volume(c
->status
);
396 return mpdclient_cmd_volume(c
, --c
->volume
);
400 mpdclient_cmd_add_path(struct mpdclient
*c
, const gchar
*path_utf8
)
402 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
403 if (connection
== NULL
)
406 return mpd_send_add(connection
, path_utf8
)?
407 mpdclient_finish_command(c
) : false;
411 mpdclient_cmd_add(struct mpdclient
*c
, const struct mpd_song
*song
)
413 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
414 struct mpd_status
*status
;
415 struct mpd_song
*new_song
;
418 assert(song
!= NULL
);
420 if (connection
== NULL
|| c
->status
== NULL
)
423 /* send the add command to mpd; at the same time, get the new
424 status (to verify the new playlist id) and the last song
425 (we hope that's the song we just added) */
427 if (!mpd_command_list_begin(connection
, true) ||
428 !mpd_send_add(connection
, mpd_song_get_uri(song
)) ||
429 !mpd_send_status(connection
) ||
430 !mpd_send_get_queue_song_pos(connection
,
431 playlist_length(&c
->playlist
)) ||
432 !mpd_command_list_end(connection
) ||
433 !mpd_response_next(connection
))
434 return mpdclient_handle_error(c
);
436 c
->events
|= MPD_IDLE_QUEUE
;
438 status
= mpdclient_recv_status(c
);
442 if (!mpd_response_next(connection
))
443 return mpdclient_handle_error(c
);
445 new_song
= mpd_recv_song(connection
);
446 if (!mpd_response_finish(connection
) || new_song
== NULL
) {
447 if (new_song
!= NULL
)
448 mpd_song_free(new_song
);
450 return mpd_connection_clear_error(connection
) ||
451 mpdclient_handle_error(c
);
454 if (mpd_status_get_queue_length(status
) == playlist_length(&c
->playlist
) + 1 &&
455 mpd_status_get_queue_version(status
) == c
->playlist
.version
+ 1) {
456 /* the cheap route: match on the new playlist length
457 and its version, we can keep our local playlist
459 c
->playlist
.version
= mpd_status_get_queue_version(status
);
461 /* the song we just received has the correct id;
462 append it to the local playlist */
463 playlist_append(&c
->playlist
, new_song
);
466 mpd_song_free(new_song
);
472 mpdclient_cmd_delete(struct mpdclient
*c
, gint idx
)
474 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
475 const struct mpd_song
*song
;
476 struct mpd_status
*status
;
478 if (connection
== NULL
|| c
->status
== NULL
)
481 if (idx
< 0 || (guint
)idx
>= playlist_length(&c
->playlist
))
484 song
= playlist_get(&c
->playlist
, idx
);
486 /* send the delete command to mpd; at the same time, get the
487 new status (to verify the playlist id) */
489 if (!mpd_command_list_begin(connection
, false) ||
490 !mpd_send_delete_id(connection
, mpd_song_get_id(song
)) ||
491 !mpd_send_status(connection
) ||
492 !mpd_command_list_end(connection
))
493 return mpdclient_handle_error(c
);
495 c
->events
|= MPD_IDLE_QUEUE
;
497 status
= mpdclient_recv_status(c
);
501 if (!mpd_response_finish(connection
))
502 return mpdclient_handle_error(c
);
504 if (mpd_status_get_queue_length(status
) == playlist_length(&c
->playlist
) - 1 &&
505 mpd_status_get_queue_version(status
) == c
->playlist
.version
+ 1) {
506 /* the cheap route: match on the new playlist length
507 and its version, we can keep our local playlist
509 c
->playlist
.version
= mpd_status_get_queue_version(status
);
511 /* remove the song from the local playlist */
512 playlist_remove(&c
->playlist
, idx
);
514 /* remove references to the song */
523 * Fallback for mpdclient_cmd_delete_range() on MPD older than 0.16.
524 * It emulates the "delete range" command with a list of simple
528 mpdclient_cmd_delete_range_fallback(struct mpdclient
*c
,
529 unsigned start
, unsigned end
)
531 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
532 if (connection
== NULL
)
535 if (!mpd_command_list_begin(connection
, false))
536 return mpdclient_handle_error(c
);
538 for (; start
< end
; --end
)
539 mpd_send_delete(connection
, start
);
541 if (!mpd_command_list_end(connection
) ||
542 !mpd_response_finish(connection
))
543 return mpdclient_handle_error(c
);
549 mpdclient_cmd_delete_range(struct mpdclient
*c
, unsigned start
, unsigned end
)
551 struct mpd_connection
*connection
;
552 struct mpd_status
*status
;
554 if (end
== start
+ 1)
555 /* if that's not really a range, we choose to use the
556 safer "deleteid" version */
557 return mpdclient_cmd_delete(c
, start
);
559 connection
= mpdclient_get_connection(c
);
560 if (connection
== NULL
)
563 if (mpd_connection_cmp_server_version(connection
, 0, 16, 0) < 0)
564 return mpdclient_cmd_delete_range_fallback(c
, start
, end
);
566 /* MPD 0.16 supports "delete" with a range argument */
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(connection
, false) ||
572 !mpd_send_delete_range(connection
, start
, end
) ||
573 !mpd_send_status(connection
) ||
574 !mpd_command_list_end(connection
))
575 return mpdclient_handle_error(c
);
577 c
->events
|= MPD_IDLE_QUEUE
;
579 status
= mpdclient_recv_status(c
);
583 if (!mpd_response_finish(connection
))
584 return mpdclient_handle_error(c
);
586 if (mpd_status_get_queue_length(status
) == playlist_length(&c
->playlist
) - (end
- start
) &&
587 mpd_status_get_queue_version(status
) == c
->playlist
.version
+ 1) {
588 /* the cheap route: match on the new playlist length
589 and its version, we can keep our local playlist
591 c
->playlist
.version
= mpd_status_get_queue_version(status
);
593 /* remove the song from the local playlist */
594 while (end
> start
) {
597 /* remove references to the song */
598 if (c
->song
== playlist_get(&c
->playlist
, end
))
601 playlist_remove(&c
->playlist
, end
);
609 mpdclient_cmd_move(struct mpdclient
*c
, unsigned dest_pos
, unsigned src_pos
)
611 struct mpd_connection
*connection
;
612 struct mpd_status
*status
;
614 if (dest_pos
== src_pos
)
617 connection
= mpdclient_get_connection(c
);
618 if (connection
== NULL
)
621 /* send the "move" command to MPD; at the same time, get the
622 new status (to verify the playlist id) */
624 if (!mpd_command_list_begin(connection
, false) ||
625 !mpd_send_move(connection
, src_pos
, dest_pos
) ||
626 !mpd_send_status(connection
) ||
627 !mpd_command_list_end(connection
))
628 return mpdclient_handle_error(c
);
630 c
->events
|= MPD_IDLE_QUEUE
;
632 status
= mpdclient_recv_status(c
);
636 if (!mpd_response_finish(connection
))
637 return mpdclient_handle_error(c
);
639 if (mpd_status_get_queue_length(status
) == playlist_length(&c
->playlist
) &&
640 mpd_status_get_queue_version(status
) == c
->playlist
.version
+ 1) {
641 /* the cheap route: match on the new playlist length
642 and its version, we can keep our local playlist
644 c
->playlist
.version
= mpd_status_get_queue_version(status
);
646 /* swap songs in the local playlist */
647 playlist_move(&c
->playlist
, dest_pos
, src_pos
);
654 /****************************************************************************/
655 /*** Playlist management functions ******************************************/
656 /****************************************************************************/
658 /* update playlist */
660 mpdclient_playlist_update(struct mpdclient
*c
)
662 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
663 struct mpd_entity
*entity
;
665 if (connection
== NULL
)
668 playlist_clear(&c
->playlist
);
670 mpd_send_list_queue_meta(connection
);
671 while ((entity
= mpd_recv_entity(connection
))) {
672 if (mpd_entity_get_type(entity
) == MPD_ENTITY_TYPE_SONG
)
673 playlist_append(&c
->playlist
, mpd_entity_get_song(entity
));
675 mpd_entity_free(entity
);
678 c
->playlist
.version
= mpd_status_get_queue_version(c
->status
);
681 return mpdclient_finish_command(c
);
684 /* update playlist (plchanges) */
686 mpdclient_playlist_update_changes(struct mpdclient
*c
)
688 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
689 struct mpd_song
*song
;
692 if (connection
== NULL
)
695 mpd_send_queue_changes_meta(connection
, c
->playlist
.version
);
697 while ((song
= mpd_recv_song(connection
)) != NULL
) {
698 int pos
= mpd_song_get_pos(song
);
700 if (pos
>= 0 && (guint
)pos
< c
->playlist
.list
->len
) {
702 playlist_replace(&c
->playlist
, pos
, song
);
705 playlist_append(&c
->playlist
, song
);
711 /* remove trailing songs */
713 length
= mpd_status_get_queue_length(c
->status
);
714 while (length
< c
->playlist
.list
->len
) {
715 guint pos
= c
->playlist
.list
->len
- 1;
717 /* Remove the last playlist entry */
718 playlist_remove(&c
->playlist
, pos
);
722 c
->playlist
.version
= mpd_status_get_queue_version(c
->status
);
724 return mpdclient_finish_command(c
);
728 /****************************************************************************/
729 /*** Filelist functions *****************************************************/
730 /****************************************************************************/
733 mpdclient_filelist_add_all(struct mpdclient
*c
, struct filelist
*fl
)
735 struct mpd_connection
*connection
= mpdclient_get_connection(c
);
738 if (connection
== NULL
)
741 if (filelist_is_empty(fl
))
744 mpd_command_list_begin(connection
, false);
746 for (i
= 0; i
< filelist_length(fl
); ++i
) {
747 struct filelist_entry
*entry
= filelist_get(fl
, i
);
748 struct mpd_entity
*entity
= entry
->entity
;
750 if (entity
!= NULL
&&
751 mpd_entity_get_type(entity
) == MPD_ENTITY_TYPE_SONG
) {
752 const struct mpd_song
*song
=
753 mpd_entity_get_song(entity
);
755 mpd_send_add(connection
, mpd_song_get_uri(song
));
759 mpd_command_list_end(connection
);
760 return mpdclient_finish_command(c
);