e1aee1cbdb2fcc49d73b82ff13125a3a43f6eb68
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 static void
122 mpdclient_status_free(struct mpdclient *c)
123 {
124 if (c->status == NULL)
125 return;
127 mpd_status_free(c->status);
128 c->status = NULL;
129 }
131 void
132 mpdclient_disconnect(struct mpdclient *c)
133 {
134 if (c->source != NULL) {
135 mpd_glib_free(c->source);
136 c->source = NULL;
137 c->idle = false;
138 }
140 if (c->connection) {
141 mpd_connection_free(c->connection);
142 ++c->connection_id;
143 }
144 c->connection = NULL;
146 mpdclient_status_free(c);
148 playlist_clear(&c->playlist);
150 if (c->song)
151 c->song = NULL;
153 /* everything has changed after a disconnect */
154 c->events |= MPD_IDLE_ALL;
155 }
157 bool
158 mpdclient_connect(struct mpdclient *c,
159 const gchar *host,
160 gint port,
161 unsigned timeout_ms,
162 const gchar *password)
163 {
164 /* close any open connection */
165 if (c->connection)
166 mpdclient_disconnect(c);
168 /* connect to MPD */
169 c->connection = mpd_connection_new(host, port, timeout_ms);
170 if (c->connection == NULL)
171 g_error("Out of memory");
173 if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
174 mpdclient_handle_error(c);
175 mpdclient_disconnect(c);
176 return false;
177 }
179 /* send password */
180 if (password != NULL && !mpd_run_password(c->connection, password)) {
181 mpdclient_handle_error(c);
182 mpdclient_disconnect(c);
183 return false;
184 }
186 c->source = mpd_glib_new(c->connection,
187 mpdclient_gidle_callback, c);
189 ++c->connection_id;
191 return true;
192 }
194 bool
195 mpdclient_update(struct mpdclient *c)
196 {
197 struct mpd_connection *connection = mpdclient_get_connection(c);
199 c->volume = -1;
201 if (connection == NULL)
202 return false;
204 /* free the old status */
205 mpdclient_status_free(c);
207 /* retrieve new status */
208 c->status = mpd_run_status(connection);
209 if (c->status == NULL)
210 return mpdclient_handle_error(c);
212 c->volume = mpd_status_get_volume(c->status);
214 /* check if the playlist needs an update */
215 if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
216 bool retval;
218 if (!playlist_is_empty(&c->playlist))
219 retval = mpdclient_playlist_update_changes(c);
220 else
221 retval = mpdclient_playlist_update(c);
222 if (!retval)
223 return false;
224 }
226 /* update the current song */
227 if (!c->song || mpd_status_get_song_id(c->status) >= 0) {
228 c->song = playlist_get_song(&c->playlist,
229 mpd_status_get_song_pos(c->status));
230 }
232 return true;
233 }
235 struct mpd_connection *
236 mpdclient_get_connection(struct mpdclient *c)
237 {
238 if (c->source != NULL && c->idle) {
239 c->idle = false;
240 mpd_glib_leave(c->source);
241 }
243 return c->connection;
244 }
246 void
247 mpdclient_put_connection(struct mpdclient *c)
248 {
249 assert(c->source == NULL || c->connection != NULL);
251 if (c->source != NULL && !c->idle) {
252 c->idle = mpd_glib_enter(c->source);
253 }
254 }
256 static struct mpd_status *
257 mpdclient_recv_status(struct mpdclient *c)
258 {
259 assert(c->connection != NULL);
261 struct mpd_status *status = mpd_recv_status(c->connection);
262 if (status == NULL) {
263 mpdclient_handle_error(c);
264 return NULL;
265 }
267 if (c->status != NULL)
268 mpd_status_free(c->status);
269 return c->status = status;
270 }
272 /****************************************************************************/
273 /*** MPD Commands **********************************************************/
274 /****************************************************************************/
276 bool
277 mpdclient_cmd_crop(struct mpdclient *c)
278 {
279 if (!mpdclient_is_playing(c))
280 return false;
282 int length = mpd_status_get_queue_length(c->status);
283 int current = mpd_status_get_song_pos(c->status);
284 if (current < 0 || mpd_status_get_queue_length(c->status) < 2)
285 return true;
287 struct mpd_connection *connection = mpdclient_get_connection(c);
288 if (connection == NULL)
289 return false;
291 mpd_command_list_begin(connection, false);
293 if (current < length - 1)
294 mpd_send_delete_range(connection, current + 1, length);
295 if (current > 0)
296 mpd_send_delete_range(connection, 0, current);
298 mpd_command_list_end(connection);
300 return mpdclient_finish_command(c);
301 }
303 bool
304 mpdclient_cmd_clear(struct mpdclient *c)
305 {
306 struct mpd_connection *connection = mpdclient_get_connection(c);
307 if (connection == NULL)
308 return false;
310 /* send "clear" and "status" */
311 if (!mpd_command_list_begin(connection, false) ||
312 !mpd_send_clear(connection) ||
313 !mpd_send_status(connection) ||
314 !mpd_command_list_end(connection))
315 return mpdclient_handle_error(c);
317 /* receive the new status, store it in the mpdclient struct */
319 struct mpd_status *status = mpdclient_recv_status(c);
320 if (status == NULL)
321 return false;
323 if (!mpd_response_finish(connection))
324 return mpdclient_handle_error(c);
326 /* update mpdclient.playlist */
328 if (mpd_status_get_queue_length(status) == 0) {
329 /* after the "clear" command, the queue is really
330 empty - this means we can clear it locally,
331 reducing the UI latency */
332 playlist_clear(&c->playlist);
333 c->playlist.version = mpd_status_get_queue_version(status);
334 c->song = NULL;
335 }
337 c->events |= MPD_IDLE_QUEUE;
338 return true;
339 }
341 bool
342 mpdclient_cmd_volume(struct mpdclient *c, gint value)
343 {
344 struct mpd_connection *connection = mpdclient_get_connection(c);
345 if (connection == NULL)
346 return false;
348 mpd_send_set_volume(connection, value);
349 return mpdclient_finish_command(c);
350 }
352 bool
353 mpdclient_cmd_volume_up(struct mpdclient *c)
354 {
355 struct mpd_connection *connection = mpdclient_get_connection(c);
356 if (connection == NULL)
357 return false;
359 if (c->status == NULL ||
360 mpd_status_get_volume(c->status) == -1)
361 return true;
363 if (c->volume < 0)
364 c->volume = mpd_status_get_volume(c->status);
366 if (c->volume >= 100)
367 return true;
369 return mpdclient_cmd_volume(c, ++c->volume);
370 }
372 bool
373 mpdclient_cmd_volume_down(struct mpdclient *c)
374 {
375 struct mpd_connection *connection = mpdclient_get_connection(c);
376 if (connection == NULL)
377 return false;
379 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
380 return true;
382 if (c->volume < 0)
383 c->volume = mpd_status_get_volume(c->status);
385 if (c->volume <= 0)
386 return true;
388 return mpdclient_cmd_volume(c, --c->volume);
389 }
391 bool
392 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
393 {
394 struct mpd_connection *connection = mpdclient_get_connection(c);
395 if (connection == NULL)
396 return false;
398 return mpd_send_add(connection, path_utf8)?
399 mpdclient_finish_command(c) : false;
400 }
402 bool
403 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
404 {
405 assert(c != NULL);
406 assert(song != NULL);
408 struct mpd_connection *connection = mpdclient_get_connection(c);
409 if (connection == NULL || c->status == NULL)
410 return false;
412 /* send the add command to mpd; at the same time, get the new
413 status (to verify the new playlist id) and the last song
414 (we hope that's the song we just added) */
416 if (!mpd_command_list_begin(connection, true) ||
417 !mpd_send_add(connection, mpd_song_get_uri(song)) ||
418 !mpd_send_status(connection) ||
419 !mpd_send_get_queue_song_pos(connection,
420 playlist_length(&c->playlist)) ||
421 !mpd_command_list_end(connection) ||
422 !mpd_response_next(connection))
423 return mpdclient_handle_error(c);
425 c->events |= MPD_IDLE_QUEUE;
427 struct mpd_status *status = mpdclient_recv_status(c);
428 if (status == NULL)
429 return false;
431 if (!mpd_response_next(connection))
432 return mpdclient_handle_error(c);
434 struct mpd_song *new_song = mpd_recv_song(connection);
435 if (!mpd_response_finish(connection) || new_song == NULL) {
436 if (new_song != NULL)
437 mpd_song_free(new_song);
439 return mpd_connection_clear_error(connection) ||
440 mpdclient_handle_error(c);
441 }
443 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
444 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
445 /* the cheap route: match on the new playlist length
446 and its version, we can keep our local playlist
447 copy in sync */
448 c->playlist.version = mpd_status_get_queue_version(status);
450 /* the song we just received has the correct id;
451 append it to the local playlist */
452 playlist_append(&c->playlist, new_song);
453 }
455 mpd_song_free(new_song);
457 return true;
458 }
460 bool
461 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
462 {
463 struct mpd_connection *connection = mpdclient_get_connection(c);
465 if (connection == NULL || c->status == NULL)
466 return false;
468 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
469 return false;
471 const struct mpd_song *song = playlist_get(&c->playlist, idx);
473 /* send the delete command to mpd; at the same time, get the
474 new status (to verify the playlist id) */
476 if (!mpd_command_list_begin(connection, false) ||
477 !mpd_send_delete_id(connection, mpd_song_get_id(song)) ||
478 !mpd_send_status(connection) ||
479 !mpd_command_list_end(connection))
480 return mpdclient_handle_error(c);
482 c->events |= MPD_IDLE_QUEUE;
484 struct mpd_status *status = mpdclient_recv_status(c);
485 if (status == NULL)
486 return false;
488 if (!mpd_response_finish(connection))
489 return mpdclient_handle_error(c);
491 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
492 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
493 /* the cheap route: match on the new playlist length
494 and its version, we can keep our local playlist
495 copy in sync */
496 c->playlist.version = mpd_status_get_queue_version(status);
498 /* remove the song from the local playlist */
499 playlist_remove(&c->playlist, idx);
501 /* remove references to the song */
502 if (c->song == song)
503 c->song = NULL;
504 }
506 return true;
507 }
509 bool
510 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
511 {
512 if (end == start + 1)
513 /* if that's not really a range, we choose to use the
514 safer "deleteid" version */
515 return mpdclient_cmd_delete(c, start);
517 struct mpd_connection *connection = mpdclient_get_connection(c);
518 if (connection == NULL)
519 return false;
521 /* send the delete command to mpd; at the same time, get the
522 new status (to verify the playlist id) */
524 if (!mpd_command_list_begin(connection, false) ||
525 !mpd_send_delete_range(connection, start, end) ||
526 !mpd_send_status(connection) ||
527 !mpd_command_list_end(connection))
528 return mpdclient_handle_error(c);
530 c->events |= MPD_IDLE_QUEUE;
532 struct mpd_status *status = mpdclient_recv_status(c);
533 if (status == NULL)
534 return false;
536 if (!mpd_response_finish(connection))
537 return mpdclient_handle_error(c);
539 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
540 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
541 /* the cheap route: match on the new playlist length
542 and its version, we can keep our local playlist
543 copy in sync */
544 c->playlist.version = mpd_status_get_queue_version(status);
546 /* remove the song from the local playlist */
547 while (end > start) {
548 --end;
550 /* remove references to the song */
551 if (c->song == playlist_get(&c->playlist, end))
552 c->song = NULL;
554 playlist_remove(&c->playlist, end);
555 }
556 }
558 return true;
559 }
561 bool
562 mpdclient_cmd_move(struct mpdclient *c, unsigned dest_pos, unsigned src_pos)
563 {
564 if (dest_pos == src_pos)
565 return true;
567 struct mpd_connection *connection = mpdclient_get_connection(c);
568 if (connection == NULL)
569 return false;
571 /* send the "move" command to MPD; at the same time, get the
572 new status (to verify the playlist id) */
574 if (!mpd_command_list_begin(connection, false) ||
575 !mpd_send_move(connection, src_pos, dest_pos) ||
576 !mpd_send_status(connection) ||
577 !mpd_command_list_end(connection))
578 return mpdclient_handle_error(c);
580 c->events |= MPD_IDLE_QUEUE;
582 struct mpd_status *status = mpdclient_recv_status(c);
583 if (status == NULL)
584 return false;
586 if (!mpd_response_finish(connection))
587 return mpdclient_handle_error(c);
589 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
590 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
591 /* the cheap route: match on the new playlist length
592 and its version, we can keep our local playlist
593 copy in sync */
594 c->playlist.version = mpd_status_get_queue_version(status);
596 /* swap songs in the local playlist */
597 playlist_move(&c->playlist, dest_pos, src_pos);
598 }
600 return true;
601 }
603 /* The client-to-client protocol (MPD 0.17.0) */
605 bool
606 mpdclient_cmd_subscribe(struct mpdclient *c, const char *channel)
607 {
608 struct mpd_connection *connection = mpdclient_get_connection(c);
610 if (connection == NULL)
611 return false;
613 if (!mpd_send_subscribe(connection, channel))
614 return mpdclient_handle_error(c);
616 return mpdclient_finish_command(c);
617 }
619 bool
620 mpdclient_cmd_unsubscribe(struct mpdclient *c, const char *channel)
621 {
622 struct mpd_connection *connection = mpdclient_get_connection(c);
623 if (connection == NULL)
624 return false;
626 if (!mpd_send_unsubscribe(connection, channel))
627 return mpdclient_handle_error(c);
629 return mpdclient_finish_command(c);
630 }
632 bool
633 mpdclient_cmd_send_message(struct mpdclient *c, const char *channel,
634 const char *text)
635 {
636 struct mpd_connection *connection = mpdclient_get_connection(c);
637 if (connection == NULL)
638 return false;
640 if (!mpd_send_send_message(connection, channel, text))
641 return mpdclient_handle_error(c);
643 return mpdclient_finish_command(c);
644 }
646 bool
647 mpdclient_send_read_messages(struct mpdclient *c)
648 {
649 struct mpd_connection *connection = mpdclient_get_connection(c);
650 if (connection == NULL)
651 return false;
653 return mpd_send_read_messages(connection)?
654 true : mpdclient_handle_error(c);
655 }
657 struct mpd_message *
658 mpdclient_recv_message(struct mpdclient *c)
659 {
660 struct mpd_connection *connection = mpdclient_get_connection(c);
661 if (connection == NULL)
662 return false;
664 struct mpd_message *message = mpd_recv_message(connection);
665 if (message == NULL &&
666 mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS)
667 mpdclient_handle_error(c);
669 return message;
670 }
672 /****************************************************************************/
673 /*** Playlist management functions ******************************************/
674 /****************************************************************************/
676 /* update playlist */
677 bool
678 mpdclient_playlist_update(struct mpdclient *c)
679 {
680 struct mpd_connection *connection = mpdclient_get_connection(c);
681 if (connection == NULL)
682 return false;
684 playlist_clear(&c->playlist);
686 mpd_send_list_queue_meta(connection);
688 struct mpd_entity *entity;
689 while ((entity = mpd_recv_entity(connection))) {
690 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
691 playlist_append(&c->playlist, mpd_entity_get_song(entity));
693 mpd_entity_free(entity);
694 }
696 c->playlist.version = mpd_status_get_queue_version(c->status);
697 c->song = NULL;
699 return mpdclient_finish_command(c);
700 }
702 /* update playlist (plchanges) */
703 bool
704 mpdclient_playlist_update_changes(struct mpdclient *c)
705 {
706 struct mpd_connection *connection = mpdclient_get_connection(c);
708 if (connection == NULL)
709 return false;
711 mpd_send_queue_changes_meta(connection, c->playlist.version);
713 struct mpd_song *song;
714 while ((song = mpd_recv_song(connection)) != NULL) {
715 int pos = mpd_song_get_pos(song);
717 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
718 /* update song */
719 playlist_replace(&c->playlist, pos, song);
720 } else {
721 /* add a new song */
722 playlist_append(&c->playlist, song);
723 }
725 mpd_song_free(song);
726 }
728 /* remove trailing songs */
730 unsigned length = mpd_status_get_queue_length(c->status);
731 while (length < c->playlist.list->len) {
732 guint pos = c->playlist.list->len - 1;
734 /* Remove the last playlist entry */
735 playlist_remove(&c->playlist, pos);
736 }
738 c->song = NULL;
739 c->playlist.version = mpd_status_get_queue_version(c->status);
741 return mpdclient_finish_command(c);
742 }
745 /****************************************************************************/
746 /*** Filelist functions *****************************************************/
747 /****************************************************************************/
749 bool
750 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
751 {
752 struct mpd_connection *connection = mpdclient_get_connection(c);
753 if (connection == NULL)
754 return false;
756 if (filelist_is_empty(fl))
757 return true;
759 mpd_command_list_begin(connection, false);
761 for (unsigned i = 0; i < filelist_length(fl); ++i) {
762 struct filelist_entry *entry = filelist_get(fl, i);
763 struct mpd_entity *entity = entry->entity;
765 if (entity != NULL &&
766 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
767 const struct mpd_song *song =
768 mpd_entity_get_song(entity);
770 mpd_send_add(connection, mpd_song_get_uri(song));
771 }
772 }
774 mpd_command_list_end(connection);
775 return mpdclient_finish_command(c);
776 }