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 gint port,
165 unsigned timeout_ms,
166 const gchar *password)
167 {
168 /* close any open connection */
169 if (c->connection)
170 mpdclient_disconnect(c);
172 /* connect to MPD */
173 c->connection = mpd_connection_new(host, port, timeout_ms);
174 if (c->connection == NULL)
175 g_error("Out of memory");
177 if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
178 mpdclient_handle_error(c);
179 mpdclient_disconnect(c);
180 mpdclient_failed_callback();
181 return false;
182 }
184 /* send password */
185 if (password != NULL && !mpd_run_password(c->connection, password)) {
186 mpdclient_handle_error(c);
187 mpdclient_disconnect(c);
188 mpdclient_failed_callback();
189 return false;
190 }
192 c->source = mpd_glib_new(c->connection,
193 mpdclient_gidle_callback, c);
195 ++c->connection_id;
197 mpdclient_connected_callback();
199 return true;
200 }
202 bool
203 mpdclient_update(struct mpdclient *c)
204 {
205 struct mpd_connection *connection = mpdclient_get_connection(c);
207 if (connection == NULL)
208 return false;
210 /* free the old status */
211 mpdclient_status_free(c);
213 /* retrieve new status */
214 c->status = mpd_run_status(connection);
215 if (c->status == NULL)
216 return mpdclient_handle_error(c);
218 c->volume = mpd_status_get_volume(c->status);
219 c->playing = mpd_status_get_state(c->status) == MPD_STATE_PLAY;
221 /* check if the playlist needs an update */
222 if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
223 bool retval;
225 if (!playlist_is_empty(&c->playlist))
226 retval = mpdclient_playlist_update_changes(c);
227 else
228 retval = mpdclient_playlist_update(c);
229 if (!retval)
230 return false;
231 }
233 /* update the current song */
234 if (!c->song || mpd_status_get_song_id(c->status) >= 0) {
235 c->song = playlist_get_song(&c->playlist,
236 mpd_status_get_song_pos(c->status));
237 }
239 return true;
240 }
242 struct mpd_connection *
243 mpdclient_get_connection(struct mpdclient *c)
244 {
245 if (c->source != NULL && c->idle) {
246 c->idle = false;
247 mpd_glib_leave(c->source);
248 }
250 return c->connection;
251 }
253 void
254 mpdclient_put_connection(struct mpdclient *c)
255 {
256 assert(c->source == NULL || c->connection != NULL);
258 if (c->source != NULL && !c->idle) {
259 c->idle = mpd_glib_enter(c->source);
260 }
261 }
263 static struct mpd_status *
264 mpdclient_recv_status(struct mpdclient *c)
265 {
266 assert(c->connection != NULL);
268 struct mpd_status *status = mpd_recv_status(c->connection);
269 if (status == NULL) {
270 mpdclient_handle_error(c);
271 return NULL;
272 }
274 if (c->status != NULL)
275 mpd_status_free(c->status);
276 return c->status = status;
277 }
279 /****************************************************************************/
280 /*** MPD Commands **********************************************************/
281 /****************************************************************************/
283 bool
284 mpdclient_cmd_crop(struct mpdclient *c)
285 {
286 if (!mpdclient_is_playing(c))
287 return false;
289 int length = mpd_status_get_queue_length(c->status);
290 int current = mpd_status_get_song_pos(c->status);
291 if (current < 0 || mpd_status_get_queue_length(c->status) < 2)
292 return true;
294 struct mpd_connection *connection = mpdclient_get_connection(c);
295 if (connection == NULL)
296 return false;
298 mpd_command_list_begin(connection, false);
300 if (current < length - 1)
301 mpd_send_delete_range(connection, current + 1, length);
302 if (current > 0)
303 mpd_send_delete_range(connection, 0, current);
305 mpd_command_list_end(connection);
307 return mpdclient_finish_command(c);
308 }
310 bool
311 mpdclient_cmd_clear(struct mpdclient *c)
312 {
313 struct mpd_connection *connection = mpdclient_get_connection(c);
314 if (connection == NULL)
315 return false;
317 /* send "clear" and "status" */
318 if (!mpd_command_list_begin(connection, false) ||
319 !mpd_send_clear(connection) ||
320 !mpd_send_status(connection) ||
321 !mpd_command_list_end(connection))
322 return mpdclient_handle_error(c);
324 /* receive the new status, store it in the mpdclient struct */
326 struct mpd_status *status = mpdclient_recv_status(c);
327 if (status == NULL)
328 return false;
330 if (!mpd_response_finish(connection))
331 return mpdclient_handle_error(c);
333 /* update mpdclient.playlist */
335 if (mpd_status_get_queue_length(status) == 0) {
336 /* after the "clear" command, the queue is really
337 empty - this means we can clear it locally,
338 reducing the UI latency */
339 playlist_clear(&c->playlist);
340 c->playlist.version = mpd_status_get_queue_version(status);
341 c->song = NULL;
342 }
344 c->events |= MPD_IDLE_QUEUE;
345 return true;
346 }
348 bool
349 mpdclient_cmd_volume(struct mpdclient *c, gint value)
350 {
351 struct mpd_connection *connection = mpdclient_get_connection(c);
352 if (connection == NULL)
353 return false;
355 mpd_send_set_volume(connection, value);
356 return mpdclient_finish_command(c);
357 }
359 bool
360 mpdclient_cmd_volume_up(struct mpdclient *c)
361 {
362 struct mpd_connection *connection = mpdclient_get_connection(c);
363 if (connection == NULL)
364 return false;
366 if (c->status == NULL ||
367 mpd_status_get_volume(c->status) == -1)
368 return true;
370 if (c->volume < 0)
371 c->volume = mpd_status_get_volume(c->status);
373 if (c->volume >= 100)
374 return true;
376 return mpdclient_cmd_volume(c, ++c->volume);
377 }
379 bool
380 mpdclient_cmd_volume_down(struct mpdclient *c)
381 {
382 struct mpd_connection *connection = mpdclient_get_connection(c);
383 if (connection == NULL)
384 return false;
386 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
387 return true;
389 if (c->volume < 0)
390 c->volume = mpd_status_get_volume(c->status);
392 if (c->volume <= 0)
393 return true;
395 return mpdclient_cmd_volume(c, --c->volume);
396 }
398 bool
399 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
400 {
401 struct mpd_connection *connection = mpdclient_get_connection(c);
402 if (connection == NULL)
403 return false;
405 return mpd_send_add(connection, path_utf8)?
406 mpdclient_finish_command(c) : false;
407 }
409 bool
410 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
411 {
412 assert(c != NULL);
413 assert(song != NULL);
415 struct mpd_connection *connection = mpdclient_get_connection(c);
416 if (connection == NULL || c->status == NULL)
417 return false;
419 /* send the add command to mpd; at the same time, get the new
420 status (to verify the new playlist id) and the last song
421 (we hope that's the song we just added) */
423 if (!mpd_command_list_begin(connection, true) ||
424 !mpd_send_add(connection, mpd_song_get_uri(song)) ||
425 !mpd_send_status(connection) ||
426 !mpd_send_get_queue_song_pos(connection,
427 playlist_length(&c->playlist)) ||
428 !mpd_command_list_end(connection) ||
429 !mpd_response_next(connection))
430 return mpdclient_handle_error(c);
432 c->events |= MPD_IDLE_QUEUE;
434 struct mpd_status *status = mpdclient_recv_status(c);
435 if (status == NULL)
436 return false;
438 if (!mpd_response_next(connection))
439 return mpdclient_handle_error(c);
441 struct mpd_song *new_song = mpd_recv_song(connection);
442 if (!mpd_response_finish(connection) || new_song == NULL) {
443 if (new_song != NULL)
444 mpd_song_free(new_song);
446 return mpd_connection_clear_error(connection) ||
447 mpdclient_handle_error(c);
448 }
450 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
451 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
452 /* the cheap route: match on the new playlist length
453 and its version, we can keep our local playlist
454 copy in sync */
455 c->playlist.version = mpd_status_get_queue_version(status);
457 /* the song we just received has the correct id;
458 append it to the local playlist */
459 playlist_append(&c->playlist, new_song);
460 }
462 mpd_song_free(new_song);
464 return true;
465 }
467 bool
468 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
469 {
470 struct mpd_connection *connection = mpdclient_get_connection(c);
472 if (connection == NULL || c->status == NULL)
473 return false;
475 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
476 return false;
478 const struct mpd_song *song = playlist_get(&c->playlist, idx);
480 /* send the delete command to mpd; at the same time, get the
481 new status (to verify the playlist id) */
483 if (!mpd_command_list_begin(connection, false) ||
484 !mpd_send_delete_id(connection, mpd_song_get_id(song)) ||
485 !mpd_send_status(connection) ||
486 !mpd_command_list_end(connection))
487 return mpdclient_handle_error(c);
489 c->events |= MPD_IDLE_QUEUE;
491 struct mpd_status *status = mpdclient_recv_status(c);
492 if (status == NULL)
493 return false;
495 if (!mpd_response_finish(connection))
496 return mpdclient_handle_error(c);
498 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
499 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
500 /* the cheap route: match on the new playlist length
501 and its version, we can keep our local playlist
502 copy in sync */
503 c->playlist.version = mpd_status_get_queue_version(status);
505 /* remove the song from the local playlist */
506 playlist_remove(&c->playlist, idx);
508 /* remove references to the song */
509 if (c->song == song)
510 c->song = NULL;
511 }
513 return true;
514 }
516 bool
517 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
518 {
519 if (end == start + 1)
520 /* if that's not really a range, we choose to use the
521 safer "deleteid" version */
522 return mpdclient_cmd_delete(c, start);
524 struct mpd_connection *connection = mpdclient_get_connection(c);
525 if (connection == NULL)
526 return false;
528 /* send the delete command to mpd; at the same time, get the
529 new status (to verify the playlist id) */
531 if (!mpd_command_list_begin(connection, false) ||
532 !mpd_send_delete_range(connection, start, end) ||
533 !mpd_send_status(connection) ||
534 !mpd_command_list_end(connection))
535 return mpdclient_handle_error(c);
537 c->events |= MPD_IDLE_QUEUE;
539 struct mpd_status *status = mpdclient_recv_status(c);
540 if (status == NULL)
541 return false;
543 if (!mpd_response_finish(connection))
544 return mpdclient_handle_error(c);
546 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
547 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
548 /* the cheap route: match on the new playlist length
549 and its version, we can keep our local playlist
550 copy in sync */
551 c->playlist.version = mpd_status_get_queue_version(status);
553 /* remove the song from the local playlist */
554 while (end > start) {
555 --end;
557 /* remove references to the song */
558 if (c->song == playlist_get(&c->playlist, end))
559 c->song = NULL;
561 playlist_remove(&c->playlist, end);
562 }
563 }
565 return true;
566 }
568 bool
569 mpdclient_cmd_move(struct mpdclient *c, unsigned dest_pos, unsigned src_pos)
570 {
571 if (dest_pos == src_pos)
572 return true;
574 struct mpd_connection *connection = mpdclient_get_connection(c);
575 if (connection == NULL)
576 return false;
578 /* send the "move" command to MPD; at the same time, get the
579 new status (to verify the playlist id) */
581 if (!mpd_command_list_begin(connection, false) ||
582 !mpd_send_move(connection, src_pos, dest_pos) ||
583 !mpd_send_status(connection) ||
584 !mpd_command_list_end(connection))
585 return mpdclient_handle_error(c);
587 c->events |= MPD_IDLE_QUEUE;
589 struct mpd_status *status = mpdclient_recv_status(c);
590 if (status == NULL)
591 return false;
593 if (!mpd_response_finish(connection))
594 return mpdclient_handle_error(c);
596 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
597 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
598 /* the cheap route: match on the new playlist length
599 and its version, we can keep our local playlist
600 copy in sync */
601 c->playlist.version = mpd_status_get_queue_version(status);
603 /* swap songs in the local playlist */
604 playlist_move(&c->playlist, dest_pos, src_pos);
605 }
607 return true;
608 }
610 /* The client-to-client protocol (MPD 0.17.0) */
612 bool
613 mpdclient_cmd_subscribe(struct mpdclient *c, const char *channel)
614 {
615 struct mpd_connection *connection = mpdclient_get_connection(c);
617 if (connection == NULL)
618 return false;
620 if (!mpd_send_subscribe(connection, channel))
621 return mpdclient_handle_error(c);
623 return mpdclient_finish_command(c);
624 }
626 bool
627 mpdclient_cmd_unsubscribe(struct mpdclient *c, const char *channel)
628 {
629 struct mpd_connection *connection = mpdclient_get_connection(c);
630 if (connection == NULL)
631 return false;
633 if (!mpd_send_unsubscribe(connection, channel))
634 return mpdclient_handle_error(c);
636 return mpdclient_finish_command(c);
637 }
639 bool
640 mpdclient_cmd_send_message(struct mpdclient *c, const char *channel,
641 const char *text)
642 {
643 struct mpd_connection *connection = mpdclient_get_connection(c);
644 if (connection == NULL)
645 return false;
647 if (!mpd_send_send_message(connection, channel, text))
648 return mpdclient_handle_error(c);
650 return mpdclient_finish_command(c);
651 }
653 bool
654 mpdclient_send_read_messages(struct mpdclient *c)
655 {
656 struct mpd_connection *connection = mpdclient_get_connection(c);
657 if (connection == NULL)
658 return false;
660 return mpd_send_read_messages(connection)?
661 true : mpdclient_handle_error(c);
662 }
664 struct mpd_message *
665 mpdclient_recv_message(struct mpdclient *c)
666 {
667 struct mpd_connection *connection = mpdclient_get_connection(c);
668 if (connection == NULL)
669 return false;
671 struct mpd_message *message = mpd_recv_message(connection);
672 if (message == NULL &&
673 mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS)
674 mpdclient_handle_error(c);
676 return message;
677 }
679 /****************************************************************************/
680 /*** Playlist management functions ******************************************/
681 /****************************************************************************/
683 /* update playlist */
684 bool
685 mpdclient_playlist_update(struct mpdclient *c)
686 {
687 struct mpd_connection *connection = mpdclient_get_connection(c);
688 if (connection == NULL)
689 return false;
691 playlist_clear(&c->playlist);
693 mpd_send_list_queue_meta(connection);
695 struct mpd_entity *entity;
696 while ((entity = mpd_recv_entity(connection))) {
697 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
698 playlist_append(&c->playlist, mpd_entity_get_song(entity));
700 mpd_entity_free(entity);
701 }
703 c->playlist.version = mpd_status_get_queue_version(c->status);
704 c->song = NULL;
706 return mpdclient_finish_command(c);
707 }
709 /* update playlist (plchanges) */
710 bool
711 mpdclient_playlist_update_changes(struct mpdclient *c)
712 {
713 struct mpd_connection *connection = mpdclient_get_connection(c);
715 if (connection == NULL)
716 return false;
718 mpd_send_queue_changes_meta(connection, c->playlist.version);
720 struct mpd_song *song;
721 while ((song = mpd_recv_song(connection)) != NULL) {
722 int pos = mpd_song_get_pos(song);
724 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
725 /* update song */
726 playlist_replace(&c->playlist, pos, song);
727 } else {
728 /* add a new song */
729 playlist_append(&c->playlist, song);
730 }
732 mpd_song_free(song);
733 }
735 /* remove trailing songs */
737 unsigned length = mpd_status_get_queue_length(c->status);
738 while (length < c->playlist.list->len) {
739 guint pos = c->playlist.list->len - 1;
741 /* Remove the last playlist entry */
742 playlist_remove(&c->playlist, pos);
743 }
745 c->song = NULL;
746 c->playlist.version = mpd_status_get_queue_version(c->status);
748 return mpdclient_finish_command(c);
749 }
752 /****************************************************************************/
753 /*** Filelist functions *****************************************************/
754 /****************************************************************************/
756 bool
757 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
758 {
759 struct mpd_connection *connection = mpdclient_get_connection(c);
760 if (connection == NULL)
761 return false;
763 if (filelist_is_empty(fl))
764 return true;
766 mpd_command_list_begin(connection, false);
768 for (unsigned i = 0; i < filelist_length(fl); ++i) {
769 struct filelist_entry *entry = filelist_get(fl, i);
770 struct mpd_entity *entity = entry->entity;
772 if (entity != NULL &&
773 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
774 const struct mpd_song *song =
775 mpd_entity_get_song(entity);
777 mpd_send_add(connection, mpd_song_get_uri(song));
778 }
779 }
781 mpd_command_list_end(connection);
782 return mpdclient_finish_command(c);
783 }