1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2017 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
4 *
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.
9 *
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.
14 *
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 "callbacks.h"
22 #include "filelist.h"
23 #include "config.h"
24 #include "gidle.h"
25 #include "charset.h"
27 #include <mpd/client.h>
29 #include <assert.h>
31 static void
32 mpdclient_invoke_error_callback(enum mpd_error error,
33 const char *message)
34 {
35 char *allocated;
36 if (error == MPD_ERROR_SERVER)
37 /* server errors are UTF-8, the others are locale */
38 message = allocated = utf8_to_locale(message);
39 else
40 allocated = NULL;
42 mpdclient_error_callback(message);
43 g_free(allocated);
44 }
46 static void
47 mpdclient_gidle_callback(enum mpd_error error,
48 gcc_unused enum mpd_server_error server_error,
49 const char *message, enum mpd_idle events,
50 void *ctx)
51 {
52 struct mpdclient *c = ctx;
54 c->idle = false;
56 assert(mpdclient_is_connected(c));
58 if (error != MPD_ERROR_SUCCESS) {
59 mpdclient_invoke_error_callback(error, message);
60 mpdclient_disconnect(c);
61 mpdclient_lost_callback();
62 return;
63 }
65 c->events |= events;
66 mpdclient_update(c);
68 mpdclient_idle_callback(c->events);
70 c->events = 0;
72 mpdclient_put_connection(c);
73 }
75 /****************************************************************************/
76 /*** mpdclient functions ****************************************************/
77 /****************************************************************************/
79 bool
80 mpdclient_handle_error(struct mpdclient *c)
81 {
82 enum mpd_error error = mpd_connection_get_error(c->connection);
84 assert(error != MPD_ERROR_SUCCESS);
86 if (error == MPD_ERROR_SERVER &&
87 mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
88 mpdclient_auth_callback(c))
89 return true;
91 mpdclient_invoke_error_callback(error,
92 mpd_connection_get_error_message(c->connection));
94 if (!mpd_connection_clear_error(c->connection))
95 mpdclient_disconnect(c);
97 return false;
98 }
100 struct mpdclient *
101 mpdclient_new(void)
102 {
103 struct mpdclient *c = g_new0(struct mpdclient, 1);
104 playlist_init(&c->playlist);
105 c->volume = -1;
106 c->events = 0;
107 c->playing = false;
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 static void
123 mpdclient_status_free(struct mpdclient *c)
124 {
125 if (c->status == NULL)
126 return;
128 mpd_status_free(c->status);
129 c->status = NULL;
131 c->volume = -1;
132 c->playing = false;
133 }
135 void
136 mpdclient_disconnect(struct mpdclient *c)
137 {
138 if (c->source != NULL) {
139 mpd_glib_free(c->source);
140 c->source = NULL;
141 c->idle = false;
142 }
144 if (c->connection) {
145 mpd_connection_free(c->connection);
146 ++c->connection_id;
147 }
148 c->connection = NULL;
150 mpdclient_status_free(c);
152 playlist_clear(&c->playlist);
154 if (c->song)
155 c->song = NULL;
157 /* everything has changed after a disconnect */
158 c->events |= MPD_IDLE_ALL;
159 }
161 bool
162 mpdclient_connect(struct mpdclient *c,
163 const gchar *host,
164 unsigned port,
165 unsigned timeout_ms,
166 const gchar *password)
167 {
168 /* close any open connection */
169 mpdclient_disconnect(c);
171 /* connect to MPD */
172 c->connection = mpd_connection_new(host, port, timeout_ms);
173 if (c->connection == NULL)
174 g_error("Out of memory");
176 if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
177 mpdclient_handle_error(c);
178 mpdclient_disconnect(c);
179 mpdclient_failed_callback();
180 return false;
181 }
183 /* send password */
184 if (password != NULL && !mpd_run_password(c->connection, password)) {
185 mpdclient_handle_error(c);
186 mpdclient_disconnect(c);
187 mpdclient_failed_callback();
188 return false;
189 }
191 c->source = mpd_glib_new(c->connection,
192 mpdclient_gidle_callback, c);
194 ++c->connection_id;
196 mpdclient_connected_callback();
198 return true;
199 }
201 bool
202 mpdclient_update(struct mpdclient *c)
203 {
204 struct mpd_connection *connection = mpdclient_get_connection(c);
206 if (connection == NULL)
207 return false;
209 /* free the old status */
210 mpdclient_status_free(c);
212 /* retrieve new status */
213 c->status = mpd_run_status(connection);
214 if (c->status == NULL)
215 return mpdclient_handle_error(c);
217 c->volume = mpd_status_get_volume(c->status);
218 c->playing = mpd_status_get_state(c->status) == MPD_STATE_PLAY;
220 /* check if the playlist needs an update */
221 if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
222 bool retval;
224 if (!playlist_is_empty(&c->playlist))
225 retval = mpdclient_playlist_update_changes(c);
226 else
227 retval = mpdclient_playlist_update(c);
228 if (!retval)
229 return false;
230 }
232 /* update the current song */
233 if (!c->song || mpd_status_get_song_id(c->status) >= 0) {
234 c->song = playlist_get_song(&c->playlist,
235 mpd_status_get_song_pos(c->status));
236 }
238 return true;
239 }
241 struct mpd_connection *
242 mpdclient_get_connection(struct mpdclient *c)
243 {
244 if (c->source != NULL && c->idle) {
245 c->idle = false;
246 mpd_glib_leave(c->source);
247 }
249 return c->connection;
250 }
252 void
253 mpdclient_put_connection(struct mpdclient *c)
254 {
255 assert(c->source == NULL || c->connection != NULL);
257 if (c->source != NULL && !c->idle) {
258 c->idle = mpd_glib_enter(c->source);
259 }
260 }
262 static struct mpd_status *
263 mpdclient_recv_status(struct mpdclient *c)
264 {
265 assert(c->connection != NULL);
267 struct mpd_status *status = mpd_recv_status(c->connection);
268 if (status == NULL) {
269 mpdclient_handle_error(c);
270 return NULL;
271 }
273 if (c->status != NULL)
274 mpd_status_free(c->status);
275 return c->status = status;
276 }
278 /****************************************************************************/
279 /*** MPD Commands **********************************************************/
280 /****************************************************************************/
282 bool
283 mpdclient_cmd_crop(struct mpdclient *c)
284 {
285 if (!mpdclient_is_playing(c))
286 return false;
288 int length = mpd_status_get_queue_length(c->status);
289 int current = mpd_status_get_song_pos(c->status);
290 if (current < 0 || mpd_status_get_queue_length(c->status) < 2)
291 return true;
293 struct mpd_connection *connection = mpdclient_get_connection(c);
294 if (connection == NULL)
295 return false;
297 mpd_command_list_begin(connection, false);
299 if (current < length - 1)
300 mpd_send_delete_range(connection, current + 1, length);
301 if (current > 0)
302 mpd_send_delete_range(connection, 0, current);
304 mpd_command_list_end(connection);
306 return mpdclient_finish_command(c);
307 }
309 bool
310 mpdclient_cmd_clear(struct mpdclient *c)
311 {
312 struct mpd_connection *connection = mpdclient_get_connection(c);
313 if (connection == NULL)
314 return false;
316 /* send "clear" and "status" */
317 if (!mpd_command_list_begin(connection, false) ||
318 !mpd_send_clear(connection) ||
319 !mpd_send_status(connection) ||
320 !mpd_command_list_end(connection))
321 return mpdclient_handle_error(c);
323 /* receive the new status, store it in the mpdclient struct */
325 struct mpd_status *status = mpdclient_recv_status(c);
326 if (status == NULL)
327 return false;
329 if (!mpd_response_finish(connection))
330 return mpdclient_handle_error(c);
332 /* update mpdclient.playlist */
334 if (mpd_status_get_queue_length(status) == 0) {
335 /* after the "clear" command, the queue is really
336 empty - this means we can clear it locally,
337 reducing the UI latency */
338 playlist_clear(&c->playlist);
339 c->playlist.version = mpd_status_get_queue_version(status);
340 c->song = NULL;
341 }
343 c->events |= MPD_IDLE_QUEUE;
344 return true;
345 }
347 bool
348 mpdclient_cmd_volume(struct mpdclient *c, gint value)
349 {
350 struct mpd_connection *connection = mpdclient_get_connection(c);
351 if (connection == NULL)
352 return false;
354 mpd_send_set_volume(connection, value);
355 return mpdclient_finish_command(c);
356 }
358 bool
359 mpdclient_cmd_volume_up(struct mpdclient *c)
360 {
361 struct mpd_connection *connection = mpdclient_get_connection(c);
362 if (connection == NULL)
363 return false;
365 if (c->status == NULL ||
366 mpd_status_get_volume(c->status) == -1)
367 return true;
369 if (c->volume < 0)
370 c->volume = mpd_status_get_volume(c->status);
372 if (c->volume >= 100)
373 return true;
375 return mpdclient_cmd_volume(c, ++c->volume);
376 }
378 bool
379 mpdclient_cmd_volume_down(struct mpdclient *c)
380 {
381 struct mpd_connection *connection = mpdclient_get_connection(c);
382 if (connection == NULL)
383 return false;
385 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
386 return true;
388 if (c->volume < 0)
389 c->volume = mpd_status_get_volume(c->status);
391 if (c->volume <= 0)
392 return true;
394 return mpdclient_cmd_volume(c, --c->volume);
395 }
397 bool
398 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
399 {
400 struct mpd_connection *connection = mpdclient_get_connection(c);
401 if (connection == NULL)
402 return false;
404 return mpd_send_add(connection, path_utf8)?
405 mpdclient_finish_command(c) : false;
406 }
408 bool
409 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
410 {
411 assert(c != NULL);
412 assert(song != NULL);
414 struct mpd_connection *connection = mpdclient_get_connection(c);
415 if (connection == NULL || c->status == NULL)
416 return false;
418 /* send the add command to mpd; at the same time, get the new
419 status (to verify the new playlist id) and the last song
420 (we hope that's the song we just added) */
422 if (!mpd_command_list_begin(connection, true) ||
423 !mpd_send_add(connection, mpd_song_get_uri(song)) ||
424 !mpd_send_status(connection) ||
425 !mpd_send_get_queue_song_pos(connection,
426 playlist_length(&c->playlist)) ||
427 !mpd_command_list_end(connection) ||
428 !mpd_response_next(connection))
429 return mpdclient_handle_error(c);
431 c->events |= MPD_IDLE_QUEUE;
433 struct mpd_status *status = mpdclient_recv_status(c);
434 if (status == NULL)
435 return false;
437 if (!mpd_response_next(connection))
438 return mpdclient_handle_error(c);
440 struct mpd_song *new_song = mpd_recv_song(connection);
441 if (!mpd_response_finish(connection) || new_song == NULL) {
442 if (new_song != NULL)
443 mpd_song_free(new_song);
445 return mpd_connection_clear_error(connection) ||
446 mpdclient_handle_error(c);
447 }
449 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
450 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
451 /* the cheap route: match on the new playlist length
452 and its version, we can keep our local playlist
453 copy in sync */
454 c->playlist.version = mpd_status_get_queue_version(status);
456 /* the song we just received has the correct id;
457 append it to the local playlist */
458 playlist_append(&c->playlist, new_song);
459 }
461 mpd_song_free(new_song);
463 return true;
464 }
466 bool
467 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
468 {
469 struct mpd_connection *connection = mpdclient_get_connection(c);
471 if (connection == NULL || c->status == NULL)
472 return false;
474 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
475 return false;
477 const struct mpd_song *song = playlist_get(&c->playlist, idx);
479 /* send the delete command to mpd; at the same time, get the
480 new status (to verify the playlist id) */
482 if (!mpd_command_list_begin(connection, false) ||
483 !mpd_send_delete_id(connection, mpd_song_get_id(song)) ||
484 !mpd_send_status(connection) ||
485 !mpd_command_list_end(connection))
486 return mpdclient_handle_error(c);
488 c->events |= MPD_IDLE_QUEUE;
490 struct mpd_status *status = mpdclient_recv_status(c);
491 if (status == NULL)
492 return false;
494 if (!mpd_response_finish(connection))
495 return mpdclient_handle_error(c);
497 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
498 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
499 /* the cheap route: match on the new playlist length
500 and its version, we can keep our local playlist
501 copy in sync */
502 c->playlist.version = mpd_status_get_queue_version(status);
504 /* remove the song from the local playlist */
505 playlist_remove(&c->playlist, idx);
507 /* remove references to the song */
508 if (c->song == song)
509 c->song = NULL;
510 }
512 return true;
513 }
515 bool
516 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
517 {
518 if (end == start + 1)
519 /* if that's not really a range, we choose to use the
520 safer "deleteid" version */
521 return mpdclient_cmd_delete(c, start);
523 struct mpd_connection *connection = mpdclient_get_connection(c);
524 if (connection == NULL)
525 return false;
527 /* send the delete command to mpd; at the same time, get the
528 new status (to verify the playlist id) */
530 if (!mpd_command_list_begin(connection, false) ||
531 !mpd_send_delete_range(connection, start, end) ||
532 !mpd_send_status(connection) ||
533 !mpd_command_list_end(connection))
534 return mpdclient_handle_error(c);
536 c->events |= MPD_IDLE_QUEUE;
538 struct mpd_status *status = mpdclient_recv_status(c);
539 if (status == NULL)
540 return false;
542 if (!mpd_response_finish(connection))
543 return mpdclient_handle_error(c);
545 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
546 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
547 /* the cheap route: match on the new playlist length
548 and its version, we can keep our local playlist
549 copy in sync */
550 c->playlist.version = mpd_status_get_queue_version(status);
552 /* remove the song from the local playlist */
553 while (end > start) {
554 --end;
556 /* remove references to the song */
557 if (c->song == playlist_get(&c->playlist, end))
558 c->song = NULL;
560 playlist_remove(&c->playlist, end);
561 }
562 }
564 return true;
565 }
567 bool
568 mpdclient_cmd_move(struct mpdclient *c, unsigned dest_pos, unsigned src_pos)
569 {
570 if (dest_pos == src_pos)
571 return true;
573 struct mpd_connection *connection = mpdclient_get_connection(c);
574 if (connection == NULL)
575 return false;
577 /* send the "move" command to MPD; at the same time, get the
578 new status (to verify the playlist id) */
580 if (!mpd_command_list_begin(connection, false) ||
581 !mpd_send_move(connection, src_pos, dest_pos) ||
582 !mpd_send_status(connection) ||
583 !mpd_command_list_end(connection))
584 return mpdclient_handle_error(c);
586 c->events |= MPD_IDLE_QUEUE;
588 struct mpd_status *status = mpdclient_recv_status(c);
589 if (status == NULL)
590 return false;
592 if (!mpd_response_finish(connection))
593 return mpdclient_handle_error(c);
595 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
596 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
597 /* the cheap route: match on the new playlist length
598 and its version, we can keep our local playlist
599 copy in sync */
600 c->playlist.version = mpd_status_get_queue_version(status);
602 /* swap songs in the local playlist */
603 playlist_move(&c->playlist, dest_pos, src_pos);
604 }
606 return true;
607 }
609 /* The client-to-client protocol (MPD 0.17.0) */
611 bool
612 mpdclient_cmd_subscribe(struct mpdclient *c, const char *channel)
613 {
614 struct mpd_connection *connection = mpdclient_get_connection(c);
616 if (connection == NULL)
617 return false;
619 if (!mpd_send_subscribe(connection, channel))
620 return mpdclient_handle_error(c);
622 return mpdclient_finish_command(c);
623 }
625 bool
626 mpdclient_cmd_unsubscribe(struct mpdclient *c, const char *channel)
627 {
628 struct mpd_connection *connection = mpdclient_get_connection(c);
629 if (connection == NULL)
630 return false;
632 if (!mpd_send_unsubscribe(connection, channel))
633 return mpdclient_handle_error(c);
635 return mpdclient_finish_command(c);
636 }
638 bool
639 mpdclient_cmd_send_message(struct mpdclient *c, const char *channel,
640 const char *text)
641 {
642 struct mpd_connection *connection = mpdclient_get_connection(c);
643 if (connection == NULL)
644 return false;
646 if (!mpd_send_send_message(connection, channel, text))
647 return mpdclient_handle_error(c);
649 return mpdclient_finish_command(c);
650 }
652 bool
653 mpdclient_send_read_messages(struct mpdclient *c)
654 {
655 struct mpd_connection *connection = mpdclient_get_connection(c);
656 if (connection == NULL)
657 return false;
659 return mpd_send_read_messages(connection)?
660 true : mpdclient_handle_error(c);
661 }
663 struct mpd_message *
664 mpdclient_recv_message(struct mpdclient *c)
665 {
666 struct mpd_connection *connection = mpdclient_get_connection(c);
667 if (connection == NULL)
668 return false;
670 struct mpd_message *message = mpd_recv_message(connection);
671 if (message == NULL &&
672 mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS)
673 mpdclient_handle_error(c);
675 return message;
676 }
678 /****************************************************************************/
679 /*** Playlist management functions ******************************************/
680 /****************************************************************************/
682 /* update playlist */
683 bool
684 mpdclient_playlist_update(struct mpdclient *c)
685 {
686 struct mpd_connection *connection = mpdclient_get_connection(c);
687 if (connection == NULL)
688 return false;
690 playlist_clear(&c->playlist);
692 mpd_send_list_queue_meta(connection);
694 struct mpd_entity *entity;
695 while ((entity = mpd_recv_entity(connection))) {
696 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
697 playlist_append(&c->playlist, mpd_entity_get_song(entity));
699 mpd_entity_free(entity);
700 }
702 c->playlist.version = mpd_status_get_queue_version(c->status);
703 c->song = NULL;
705 return mpdclient_finish_command(c);
706 }
708 /* update playlist (plchanges) */
709 bool
710 mpdclient_playlist_update_changes(struct mpdclient *c)
711 {
712 struct mpd_connection *connection = mpdclient_get_connection(c);
714 if (connection == NULL)
715 return false;
717 mpd_send_queue_changes_meta(connection, c->playlist.version);
719 struct mpd_song *song;
720 while ((song = mpd_recv_song(connection)) != NULL) {
721 int pos = mpd_song_get_pos(song);
723 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
724 /* update song */
725 playlist_replace(&c->playlist, pos, song);
726 } else {
727 /* add a new song */
728 playlist_append(&c->playlist, song);
729 }
731 mpd_song_free(song);
732 }
734 /* remove trailing songs */
736 unsigned length = mpd_status_get_queue_length(c->status);
737 while (length < c->playlist.list->len) {
738 guint pos = c->playlist.list->len - 1;
740 /* Remove the last playlist entry */
741 playlist_remove(&c->playlist, pos);
742 }
744 c->song = NULL;
745 c->playlist.version = mpd_status_get_queue_version(c->status);
747 return mpdclient_finish_command(c);
748 }
751 /****************************************************************************/
752 /*** Filelist functions *****************************************************/
753 /****************************************************************************/
755 bool
756 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
757 {
758 struct mpd_connection *connection = mpdclient_get_connection(c);
759 if (connection == NULL)
760 return false;
762 if (filelist_is_empty(fl))
763 return true;
765 mpd_command_list_begin(connection, false);
767 for (unsigned i = 0; i < filelist_length(fl); ++i) {
768 struct filelist_entry *entry = filelist_get(fl, i);
769 struct mpd_entity *entity = entry->entity;
771 if (entity != NULL &&
772 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
773 const struct mpd_song *song =
774 mpd_entity_get_song(entity);
776 mpd_send_add(connection, mpd_song_get_uri(song));
777 }
778 }
780 mpd_command_list_end(connection);
781 return mpdclient_finish_command(c);
782 }