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;
108 return c;
109 }
111 void
112 mpdclient_free(struct mpdclient *c)
113 {
114 mpdclient_disconnect(c);
116 mpdclient_playlist_free(&c->playlist);
118 g_free(c);
119 }
121 void
122 mpdclient_disconnect(struct mpdclient *c)
123 {
124 if (c->source != NULL) {
125 mpd_glib_free(c->source);
126 c->source = NULL;
127 c->idle = false;
128 }
130 if (c->connection) {
131 mpd_connection_free(c->connection);
132 ++c->connection_id;
133 }
134 c->connection = NULL;
136 if (c->status)
137 mpd_status_free(c->status);
138 c->status = NULL;
140 playlist_clear(&c->playlist);
142 if (c->song)
143 c->song = NULL;
145 /* everything has changed after a disconnect */
146 c->events |= MPD_IDLE_ALL;
147 }
149 bool
150 mpdclient_connect(struct mpdclient *c,
151 const gchar *host,
152 gint port,
153 unsigned timeout_ms,
154 const gchar *password)
155 {
156 /* close any open connection */
157 if (c->connection)
158 mpdclient_disconnect(c);
160 /* connect to MPD */
161 c->connection = mpd_connection_new(host, port, timeout_ms);
162 if (c->connection == NULL)
163 g_error("Out of memory");
165 if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
166 mpdclient_handle_error(c);
167 mpdclient_disconnect(c);
168 return false;
169 }
171 /* send password */
172 if (password != NULL && !mpd_run_password(c->connection, password)) {
173 mpdclient_handle_error(c);
174 mpdclient_disconnect(c);
175 return false;
176 }
178 c->source = mpd_glib_new(c->connection,
179 mpdclient_gidle_callback, c);
181 ++c->connection_id;
183 return true;
184 }
186 bool
187 mpdclient_update(struct mpdclient *c)
188 {
189 struct mpd_connection *connection = mpdclient_get_connection(c);
191 c->volume = -1;
193 if (connection == NULL)
194 return false;
196 /* free the old status */
197 if (c->status)
198 mpd_status_free(c->status);
200 /* retrieve new status */
201 c->status = mpd_run_status(connection);
202 if (c->status == NULL)
203 return mpdclient_handle_error(c);
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)) {
209 bool retval;
211 if (!playlist_is_empty(&c->playlist))
212 retval = mpdclient_playlist_update_changes(c);
213 else
214 retval = mpdclient_playlist_update(c);
215 if (!retval)
216 return false;
217 }
219 /* update the current song */
220 if (!c->song || mpd_status_get_song_id(c->status) >= 0) {
221 c->song = playlist_get_song(&c->playlist,
222 mpd_status_get_song_pos(c->status));
223 }
225 return true;
226 }
228 struct mpd_connection *
229 mpdclient_get_connection(struct mpdclient *c)
230 {
231 if (c->source != NULL && c->idle) {
232 c->idle = false;
233 mpd_glib_leave(c->source);
234 }
236 return c->connection;
237 }
239 void
240 mpdclient_put_connection(struct mpdclient *c)
241 {
242 assert(c->source == NULL || c->connection != NULL);
244 if (c->source != NULL && !c->idle) {
245 c->idle = mpd_glib_enter(c->source);
246 }
247 }
249 static struct mpd_status *
250 mpdclient_recv_status(struct mpdclient *c)
251 {
252 assert(c->connection != NULL);
254 struct mpd_status *status = mpd_recv_status(c->connection);
255 if (status == NULL) {
256 mpdclient_handle_error(c);
257 return NULL;
258 }
260 if (c->status != NULL)
261 mpd_status_free(c->status);
262 return c->status = status;
263 }
265 /****************************************************************************/
266 /*** MPD Commands **********************************************************/
267 /****************************************************************************/
269 bool
270 mpdclient_cmd_crop(struct mpdclient *c)
271 {
272 if (!mpdclient_is_playing(c))
273 return false;
275 int length = mpd_status_get_queue_length(c->status);
276 int current = mpd_status_get_song_pos(c->status);
277 if (current < 0 || mpd_status_get_queue_length(c->status) < 2)
278 return true;
280 struct mpd_connection *connection = mpdclient_get_connection(c);
281 if (connection == NULL)
282 return false;
284 mpd_command_list_begin(connection, false);
286 if (current < length - 1)
287 mpd_send_delete_range(connection, current + 1, length);
288 if (current > 0)
289 mpd_send_delete_range(connection, 0, current);
291 mpd_command_list_end(connection);
293 return mpdclient_finish_command(c);
294 }
296 bool
297 mpdclient_cmd_clear(struct mpdclient *c)
298 {
299 struct mpd_connection *connection = mpdclient_get_connection(c);
300 if (connection == NULL)
301 return false;
303 /* send "clear" and "status" */
304 if (!mpd_command_list_begin(connection, false) ||
305 !mpd_send_clear(connection) ||
306 !mpd_send_status(connection) ||
307 !mpd_command_list_end(connection))
308 return mpdclient_handle_error(c);
310 /* receive the new status, store it in the mpdclient struct */
312 struct mpd_status *status = mpdclient_recv_status(c);
313 if (status == NULL)
314 return false;
316 if (!mpd_response_finish(connection))
317 return mpdclient_handle_error(c);
319 /* update mpdclient.playlist */
321 if (mpd_status_get_queue_length(status) == 0) {
322 /* after the "clear" command, the queue is really
323 empty - this means we can clear it locally,
324 reducing the UI latency */
325 playlist_clear(&c->playlist);
326 c->playlist.version = mpd_status_get_queue_version(status);
327 c->song = NULL;
328 }
330 c->events |= MPD_IDLE_QUEUE;
331 return true;
332 }
334 bool
335 mpdclient_cmd_volume(struct mpdclient *c, gint value)
336 {
337 struct mpd_connection *connection = mpdclient_get_connection(c);
338 if (connection == NULL)
339 return false;
341 mpd_send_set_volume(connection, value);
342 return mpdclient_finish_command(c);
343 }
345 bool
346 mpdclient_cmd_volume_up(struct mpdclient *c)
347 {
348 struct mpd_connection *connection = mpdclient_get_connection(c);
349 if (connection == NULL)
350 return false;
352 if (c->status == NULL ||
353 mpd_status_get_volume(c->status) == -1)
354 return true;
356 if (c->volume < 0)
357 c->volume = mpd_status_get_volume(c->status);
359 if (c->volume >= 100)
360 return true;
362 return mpdclient_cmd_volume(c, ++c->volume);
363 }
365 bool
366 mpdclient_cmd_volume_down(struct mpdclient *c)
367 {
368 struct mpd_connection *connection = mpdclient_get_connection(c);
369 if (connection == NULL)
370 return false;
372 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
373 return true;
375 if (c->volume < 0)
376 c->volume = mpd_status_get_volume(c->status);
378 if (c->volume <= 0)
379 return true;
381 return mpdclient_cmd_volume(c, --c->volume);
382 }
384 bool
385 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
386 {
387 struct mpd_connection *connection = mpdclient_get_connection(c);
388 if (connection == NULL)
389 return false;
391 return mpd_send_add(connection, path_utf8)?
392 mpdclient_finish_command(c) : false;
393 }
395 bool
396 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
397 {
398 assert(c != NULL);
399 assert(song != NULL);
401 struct mpd_connection *connection = mpdclient_get_connection(c);
402 if (connection == NULL || c->status == NULL)
403 return false;
405 /* send the add command to mpd; at the same time, get the new
406 status (to verify the new playlist id) and the last song
407 (we hope that's the song we just added) */
409 if (!mpd_command_list_begin(connection, true) ||
410 !mpd_send_add(connection, mpd_song_get_uri(song)) ||
411 !mpd_send_status(connection) ||
412 !mpd_send_get_queue_song_pos(connection,
413 playlist_length(&c->playlist)) ||
414 !mpd_command_list_end(connection) ||
415 !mpd_response_next(connection))
416 return mpdclient_handle_error(c);
418 c->events |= MPD_IDLE_QUEUE;
420 struct mpd_status *status = mpdclient_recv_status(c);
421 if (status == NULL)
422 return false;
424 if (!mpd_response_next(connection))
425 return mpdclient_handle_error(c);
427 struct mpd_song *new_song = mpd_recv_song(connection);
428 if (!mpd_response_finish(connection) || new_song == NULL) {
429 if (new_song != NULL)
430 mpd_song_free(new_song);
432 return mpd_connection_clear_error(connection) ||
433 mpdclient_handle_error(c);
434 }
436 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
437 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
438 /* the cheap route: match on the new playlist length
439 and its version, we can keep our local playlist
440 copy in sync */
441 c->playlist.version = mpd_status_get_queue_version(status);
443 /* the song we just received has the correct id;
444 append it to the local playlist */
445 playlist_append(&c->playlist, new_song);
446 }
448 mpd_song_free(new_song);
450 return true;
451 }
453 bool
454 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
455 {
456 struct mpd_connection *connection = mpdclient_get_connection(c);
458 if (connection == NULL || c->status == NULL)
459 return false;
461 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
462 return false;
464 const struct mpd_song *song = playlist_get(&c->playlist, idx);
466 /* send the delete command to mpd; at the same time, get the
467 new status (to verify the playlist id) */
469 if (!mpd_command_list_begin(connection, false) ||
470 !mpd_send_delete_id(connection, mpd_song_get_id(song)) ||
471 !mpd_send_status(connection) ||
472 !mpd_command_list_end(connection))
473 return mpdclient_handle_error(c);
475 c->events |= MPD_IDLE_QUEUE;
477 struct mpd_status *status = mpdclient_recv_status(c);
478 if (status == NULL)
479 return false;
481 if (!mpd_response_finish(connection))
482 return mpdclient_handle_error(c);
484 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
485 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
486 /* the cheap route: match on the new playlist length
487 and its version, we can keep our local playlist
488 copy in sync */
489 c->playlist.version = mpd_status_get_queue_version(status);
491 /* remove the song from the local playlist */
492 playlist_remove(&c->playlist, idx);
494 /* remove references to the song */
495 if (c->song == song)
496 c->song = NULL;
497 }
499 return true;
500 }
502 bool
503 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
504 {
505 if (end == start + 1)
506 /* if that's not really a range, we choose to use the
507 safer "deleteid" version */
508 return mpdclient_cmd_delete(c, start);
510 struct mpd_connection *connection = mpdclient_get_connection(c);
511 if (connection == NULL)
512 return false;
514 /* send the delete command to mpd; at the same time, get the
515 new status (to verify the playlist id) */
517 if (!mpd_command_list_begin(connection, false) ||
518 !mpd_send_delete_range(connection, start, end) ||
519 !mpd_send_status(connection) ||
520 !mpd_command_list_end(connection))
521 return mpdclient_handle_error(c);
523 c->events |= MPD_IDLE_QUEUE;
525 struct mpd_status *status = mpdclient_recv_status(c);
526 if (status == NULL)
527 return false;
529 if (!mpd_response_finish(connection))
530 return mpdclient_handle_error(c);
532 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
533 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
534 /* the cheap route: match on the new playlist length
535 and its version, we can keep our local playlist
536 copy in sync */
537 c->playlist.version = mpd_status_get_queue_version(status);
539 /* remove the song from the local playlist */
540 while (end > start) {
541 --end;
543 /* remove references to the song */
544 if (c->song == playlist_get(&c->playlist, end))
545 c->song = NULL;
547 playlist_remove(&c->playlist, end);
548 }
549 }
551 return true;
552 }
554 bool
555 mpdclient_cmd_move(struct mpdclient *c, unsigned dest_pos, unsigned src_pos)
556 {
557 if (dest_pos == src_pos)
558 return true;
560 struct mpd_connection *connection = mpdclient_get_connection(c);
561 if (connection == NULL)
562 return false;
564 /* send the "move" command to MPD; at the same time, get the
565 new status (to verify the playlist id) */
567 if (!mpd_command_list_begin(connection, false) ||
568 !mpd_send_move(connection, src_pos, dest_pos) ||
569 !mpd_send_status(connection) ||
570 !mpd_command_list_end(connection))
571 return mpdclient_handle_error(c);
573 c->events |= MPD_IDLE_QUEUE;
575 struct mpd_status *status = mpdclient_recv_status(c);
576 if (status == NULL)
577 return false;
579 if (!mpd_response_finish(connection))
580 return mpdclient_handle_error(c);
582 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
583 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
584 /* the cheap route: match on the new playlist length
585 and its version, we can keep our local playlist
586 copy in sync */
587 c->playlist.version = mpd_status_get_queue_version(status);
589 /* swap songs in the local playlist */
590 playlist_move(&c->playlist, dest_pos, src_pos);
591 }
593 return true;
594 }
596 /* The client-to-client protocol (MPD 0.17.0) */
598 bool
599 mpdclient_cmd_subscribe(struct mpdclient *c, const char *channel)
600 {
601 struct mpd_connection *connection = mpdclient_get_connection(c);
603 if (connection == NULL)
604 return false;
606 if (!mpd_send_subscribe(connection, channel))
607 return mpdclient_handle_error(c);
609 return mpdclient_finish_command(c);
610 }
612 bool
613 mpdclient_cmd_unsubscribe(struct mpdclient *c, const char *channel)
614 {
615 struct mpd_connection *connection = mpdclient_get_connection(c);
616 if (connection == NULL)
617 return false;
619 if (!mpd_send_unsubscribe(connection, channel))
620 return mpdclient_handle_error(c);
622 return mpdclient_finish_command(c);
623 }
625 bool
626 mpdclient_cmd_send_message(struct mpdclient *c, const char *channel,
627 const char *text)
628 {
629 struct mpd_connection *connection = mpdclient_get_connection(c);
630 if (connection == NULL)
631 return false;
633 if (!mpd_send_send_message(connection, channel, text))
634 return mpdclient_handle_error(c);
636 return mpdclient_finish_command(c);
637 }
639 bool
640 mpdclient_send_read_messages(struct mpdclient *c)
641 {
642 struct mpd_connection *connection = mpdclient_get_connection(c);
643 if (connection == NULL)
644 return false;
646 return mpd_send_read_messages(connection)?
647 true : mpdclient_handle_error(c);
648 }
650 struct mpd_message *
651 mpdclient_recv_message(struct mpdclient *c)
652 {
653 struct mpd_connection *connection = mpdclient_get_connection(c);
654 if (connection == NULL)
655 return false;
657 struct mpd_message *message = mpd_recv_message(connection);
658 if (message == NULL &&
659 mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS)
660 mpdclient_handle_error(c);
662 return message;
663 }
665 /****************************************************************************/
666 /*** Playlist management functions ******************************************/
667 /****************************************************************************/
669 /* update playlist */
670 bool
671 mpdclient_playlist_update(struct mpdclient *c)
672 {
673 struct mpd_connection *connection = mpdclient_get_connection(c);
674 if (connection == NULL)
675 return false;
677 playlist_clear(&c->playlist);
679 mpd_send_list_queue_meta(connection);
681 struct mpd_entity *entity;
682 while ((entity = mpd_recv_entity(connection))) {
683 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
684 playlist_append(&c->playlist, mpd_entity_get_song(entity));
686 mpd_entity_free(entity);
687 }
689 c->playlist.version = mpd_status_get_queue_version(c->status);
690 c->song = NULL;
692 return mpdclient_finish_command(c);
693 }
695 /* update playlist (plchanges) */
696 bool
697 mpdclient_playlist_update_changes(struct mpdclient *c)
698 {
699 struct mpd_connection *connection = mpdclient_get_connection(c);
701 if (connection == NULL)
702 return false;
704 mpd_send_queue_changes_meta(connection, c->playlist.version);
706 struct mpd_song *song;
707 while ((song = mpd_recv_song(connection)) != NULL) {
708 int pos = mpd_song_get_pos(song);
710 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
711 /* update song */
712 playlist_replace(&c->playlist, pos, song);
713 } else {
714 /* add a new song */
715 playlist_append(&c->playlist, song);
716 }
718 mpd_song_free(song);
719 }
721 /* remove trailing songs */
723 unsigned length = mpd_status_get_queue_length(c->status);
724 while (length < c->playlist.list->len) {
725 guint pos = c->playlist.list->len - 1;
727 /* Remove the last playlist entry */
728 playlist_remove(&c->playlist, pos);
729 }
731 c->song = NULL;
732 c->playlist.version = mpd_status_get_queue_version(c->status);
734 return mpdclient_finish_command(c);
735 }
738 /****************************************************************************/
739 /*** Filelist functions *****************************************************/
740 /****************************************************************************/
742 bool
743 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
744 {
745 struct mpd_connection *connection = mpdclient_get_connection(c);
746 if (connection == NULL)
747 return false;
749 if (filelist_is_empty(fl))
750 return true;
752 mpd_command_list_begin(connection, false);
754 for (unsigned i = 0; i < filelist_length(fl); ++i) {
755 struct filelist_entry *entry = filelist_get(fl, i);
756 struct mpd_entity *entity = entry->entity;
758 if (entity != NULL &&
759 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
760 const struct mpd_song *song =
761 mpd_entity_get_song(entity);
763 mpd_send_add(connection, mpd_song_get_uri(song));
764 }
765 }
767 mpd_command_list_end(connection);
768 return mpdclient_finish_command(c);
769 }