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 "options.h"
25 #include "strfsong.h"
26 #include "utils.h"
27 #include "gidle.h"
29 #include <mpd/client.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <time.h>
34 #include <string.h>
36 #define BUFSIZE 1024
38 /* sort by song format */
39 gint
40 compare_filelistentry_format(gconstpointer filelist_entry1,
41 gconstpointer filelist_entry2,
42 const char *song_format)
43 {
44 const struct mpd_entity *e1 =
45 ((const struct filelist_entry *)filelist_entry1)->entity;
46 const struct mpd_entity *e2 =
47 ((const struct filelist_entry *)filelist_entry2)->entity;
49 int n = 0;
50 if (e1 && e2 &&
51 mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG &&
52 mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) {
53 char key1[BUFSIZE], key2[BUFSIZE];
54 strfsong(key1, BUFSIZE, song_format, mpd_entity_get_song(e1));
55 strfsong(key2, BUFSIZE, song_format, mpd_entity_get_song(e2));
56 n = strcmp(key1,key2);
57 }
59 return n;
60 }
63 /****************************************************************************/
64 /*** mpdclient functions ****************************************************/
65 /****************************************************************************/
67 bool
68 mpdclient_handle_error(struct mpdclient *c)
69 {
70 enum mpd_error error = mpd_connection_get_error(c->connection);
72 assert(error != MPD_ERROR_SUCCESS);
74 if (error == MPD_ERROR_SERVER &&
75 mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
76 mpdclient_auth_callback(c))
77 return true;
79 mpdclient_error_callback(mpd_connection_get_error_message(c->connection));
81 if (!mpd_connection_clear_error(c->connection))
82 mpdclient_disconnect(c);
84 return false;
85 }
87 struct mpdclient *
88 mpdclient_new(void)
89 {
90 struct mpdclient *c = g_new0(struct mpdclient, 1);
91 playlist_init(&c->playlist);
92 c->volume = -1;
93 c->events = 0;
95 return c;
96 }
98 void
99 mpdclient_free(struct mpdclient *c)
100 {
101 mpdclient_disconnect(c);
103 mpdclient_playlist_free(&c->playlist);
105 g_free(c);
106 }
108 void
109 mpdclient_disconnect(struct mpdclient *c)
110 {
111 if (c->source != NULL) {
112 mpd_glib_free(c->source);
113 c->source = NULL;
114 c->idle = false;
115 }
117 if (c->connection) {
118 mpd_connection_free(c->connection);
119 ++c->connection_id;
120 }
121 c->connection = NULL;
123 if (c->status)
124 mpd_status_free(c->status);
125 c->status = NULL;
127 playlist_clear(&c->playlist);
129 if (c->song)
130 c->song = NULL;
132 /* everything has changed after a disconnect */
133 c->events |= MPD_IDLE_ALL;
134 }
136 bool
137 mpdclient_connect(struct mpdclient *c,
138 const gchar *host,
139 gint port,
140 unsigned timeout_ms,
141 const gchar *password)
142 {
143 /* close any open connection */
144 if (c->connection)
145 mpdclient_disconnect(c);
147 /* connect to MPD */
148 c->connection = mpd_connection_new(host, port, timeout_ms);
149 if (c->connection == NULL)
150 g_error("Out of memory");
152 if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
153 mpdclient_handle_error(c);
154 mpdclient_disconnect(c);
155 return false;
156 }
158 /* send password */
159 if (password != NULL && !mpd_run_password(c->connection, password)) {
160 mpdclient_handle_error(c);
161 mpdclient_disconnect(c);
162 return false;
163 }
165 c->source = mpd_glib_new(c->connection,
166 mpdclient_idle_callback, c);
168 ++c->connection_id;
170 return true;
171 }
173 bool
174 mpdclient_update(struct mpdclient *c)
175 {
176 struct mpd_connection *connection = mpdclient_get_connection(c);
178 c->volume = -1;
180 if (connection == NULL)
181 return false;
183 /* free the old status */
184 if (c->status)
185 mpd_status_free(c->status);
187 /* retrieve new status */
188 c->status = mpd_run_status(connection);
189 if (c->status == NULL)
190 return mpdclient_handle_error(c);
192 c->update_id = mpd_status_get_update_id(c->status);
194 c->volume = mpd_status_get_volume(c->status);
196 /* check if the playlist needs an update */
197 if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
198 bool retval;
200 if (!playlist_is_empty(&c->playlist))
201 retval = mpdclient_playlist_update_changes(c);
202 else
203 retval = mpdclient_playlist_update(c);
204 if (!retval)
205 return false;
206 }
208 /* update the current song */
209 if (!c->song || mpd_status_get_song_id(c->status) >= 0) {
210 c->song = playlist_get_song(&c->playlist,
211 mpd_status_get_song_pos(c->status));
212 }
214 return true;
215 }
217 struct mpd_connection *
218 mpdclient_get_connection(struct mpdclient *c)
219 {
220 if (c->source != NULL && c->idle) {
221 c->idle = false;
222 mpd_glib_leave(c->source);
223 }
225 return c->connection;
226 }
228 void
229 mpdclient_put_connection(struct mpdclient *c)
230 {
231 assert(c->source == NULL || c->connection != NULL);
233 if (c->source != NULL && !c->idle) {
234 c->idle = mpd_glib_enter(c->source);
235 }
236 }
238 static struct mpd_status *
239 mpdclient_recv_status(struct mpdclient *c)
240 {
241 assert(c->connection != NULL);
243 struct mpd_status *status = mpd_recv_status(c->connection);
244 if (status == NULL) {
245 mpdclient_handle_error(c);
246 return NULL;
247 }
249 if (c->status != NULL)
250 mpd_status_free(c->status);
251 return c->status = status;
252 }
254 /****************************************************************************/
255 /*** MPD Commands **********************************************************/
256 /****************************************************************************/
258 bool
259 mpdclient_cmd_crop(struct mpdclient *c)
260 {
261 if (!mpdclient_is_playing(c))
262 return false;
264 int length = mpd_status_get_queue_length(c->status);
265 int current = mpd_status_get_song_pos(c->status);
266 if (current < 0 || mpd_status_get_queue_length(c->status) < 2)
267 return true;
269 struct mpd_connection *connection = mpdclient_get_connection(c);
270 if (connection == NULL)
271 return false;
273 mpd_command_list_begin(connection, false);
275 if (current < length - 1)
276 mpd_send_delete_range(connection, current + 1, length);
277 if (current > 0)
278 mpd_send_delete_range(connection, 0, current);
280 mpd_command_list_end(connection);
282 return mpdclient_finish_command(c);
283 }
285 bool
286 mpdclient_cmd_clear(struct mpdclient *c)
287 {
288 struct mpd_connection *connection = mpdclient_get_connection(c);
289 if (connection == NULL)
290 return false;
292 /* send "clear" and "status" */
293 if (!mpd_command_list_begin(connection, false) ||
294 !mpd_send_clear(connection) ||
295 !mpd_send_status(connection) ||
296 !mpd_command_list_end(connection))
297 return mpdclient_handle_error(c);
299 /* receive the new status, store it in the mpdclient struct */
301 struct mpd_status *status = mpdclient_recv_status(c);
302 if (status == NULL)
303 return false;
305 if (!mpd_response_finish(connection))
306 return mpdclient_handle_error(c);
308 /* update mpdclient.playlist */
310 if (mpd_status_get_queue_length(status) == 0) {
311 /* after the "clear" command, the queue is really
312 empty - this means we can clear it locally,
313 reducing the UI latency */
314 playlist_clear(&c->playlist);
315 c->playlist.version = mpd_status_get_queue_version(status);
316 c->song = NULL;
317 }
319 c->events |= MPD_IDLE_QUEUE;
320 return true;
321 }
323 bool
324 mpdclient_cmd_volume(struct mpdclient *c, gint value)
325 {
326 struct mpd_connection *connection = mpdclient_get_connection(c);
327 if (connection == NULL)
328 return false;
330 mpd_send_set_volume(connection, value);
331 return mpdclient_finish_command(c);
332 }
334 bool
335 mpdclient_cmd_volume_up(struct mpdclient *c)
336 {
337 struct mpd_connection *connection = mpdclient_get_connection(c);
338 if (connection == NULL)
339 return false;
341 if (c->status == NULL ||
342 mpd_status_get_volume(c->status) == -1)
343 return true;
345 if (c->volume < 0)
346 c->volume = mpd_status_get_volume(c->status);
348 if (c->volume >= 100)
349 return true;
351 return mpdclient_cmd_volume(c, ++c->volume);
352 }
354 bool
355 mpdclient_cmd_volume_down(struct mpdclient *c)
356 {
357 struct mpd_connection *connection = mpdclient_get_connection(c);
358 if (connection == NULL)
359 return false;
361 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
362 return true;
364 if (c->volume < 0)
365 c->volume = mpd_status_get_volume(c->status);
367 if (c->volume <= 0)
368 return true;
370 return mpdclient_cmd_volume(c, --c->volume);
371 }
373 bool
374 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
375 {
376 struct mpd_connection *connection = mpdclient_get_connection(c);
377 if (connection == NULL)
378 return false;
380 return mpd_send_add(connection, path_utf8)?
381 mpdclient_finish_command(c) : false;
382 }
384 bool
385 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
386 {
387 assert(c != NULL);
388 assert(song != NULL);
390 struct mpd_connection *connection = mpdclient_get_connection(c);
391 if (connection == NULL || c->status == NULL)
392 return false;
394 /* send the add command to mpd; at the same time, get the new
395 status (to verify the new playlist id) and the last song
396 (we hope that's the song we just added) */
398 if (!mpd_command_list_begin(connection, true) ||
399 !mpd_send_add(connection, mpd_song_get_uri(song)) ||
400 !mpd_send_status(connection) ||
401 !mpd_send_get_queue_song_pos(connection,
402 playlist_length(&c->playlist)) ||
403 !mpd_command_list_end(connection) ||
404 !mpd_response_next(connection))
405 return mpdclient_handle_error(c);
407 c->events |= MPD_IDLE_QUEUE;
409 struct mpd_status *status = mpdclient_recv_status(c);
410 if (status == NULL)
411 return false;
413 if (!mpd_response_next(connection))
414 return mpdclient_handle_error(c);
416 struct mpd_song *new_song = mpd_recv_song(connection);
417 if (!mpd_response_finish(connection) || new_song == NULL) {
418 if (new_song != NULL)
419 mpd_song_free(new_song);
421 return mpd_connection_clear_error(connection) ||
422 mpdclient_handle_error(c);
423 }
425 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
426 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
427 /* the cheap route: match on the new playlist length
428 and its version, we can keep our local playlist
429 copy in sync */
430 c->playlist.version = mpd_status_get_queue_version(status);
432 /* the song we just received has the correct id;
433 append it to the local playlist */
434 playlist_append(&c->playlist, new_song);
435 }
437 mpd_song_free(new_song);
439 return true;
440 }
442 bool
443 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
444 {
445 struct mpd_connection *connection = mpdclient_get_connection(c);
447 if (connection == NULL || c->status == NULL)
448 return false;
450 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
451 return false;
453 const struct mpd_song *song = playlist_get(&c->playlist, idx);
455 /* send the delete command to mpd; at the same time, get the
456 new status (to verify the playlist id) */
458 if (!mpd_command_list_begin(connection, false) ||
459 !mpd_send_delete_id(connection, mpd_song_get_id(song)) ||
460 !mpd_send_status(connection) ||
461 !mpd_command_list_end(connection))
462 return mpdclient_handle_error(c);
464 c->events |= MPD_IDLE_QUEUE;
466 struct mpd_status *status = mpdclient_recv_status(c);
467 if (status == NULL)
468 return false;
470 if (!mpd_response_finish(connection))
471 return mpdclient_handle_error(c);
473 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
474 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
475 /* the cheap route: match on the new playlist length
476 and its version, we can keep our local playlist
477 copy in sync */
478 c->playlist.version = mpd_status_get_queue_version(status);
480 /* remove the song from the local playlist */
481 playlist_remove(&c->playlist, idx);
483 /* remove references to the song */
484 if (c->song == song)
485 c->song = NULL;
486 }
488 return true;
489 }
491 bool
492 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
493 {
494 if (end == start + 1)
495 /* if that's not really a range, we choose to use the
496 safer "deleteid" version */
497 return mpdclient_cmd_delete(c, start);
499 struct mpd_connection *connection = mpdclient_get_connection(c);
500 if (connection == NULL)
501 return false;
503 /* send the delete command to mpd; at the same time, get the
504 new status (to verify the playlist id) */
506 if (!mpd_command_list_begin(connection, false) ||
507 !mpd_send_delete_range(connection, start, end) ||
508 !mpd_send_status(connection) ||
509 !mpd_command_list_end(connection))
510 return mpdclient_handle_error(c);
512 c->events |= MPD_IDLE_QUEUE;
514 struct mpd_status *status = mpdclient_recv_status(c);
515 if (status == NULL)
516 return false;
518 if (!mpd_response_finish(connection))
519 return mpdclient_handle_error(c);
521 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
522 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
523 /* the cheap route: match on the new playlist length
524 and its version, we can keep our local playlist
525 copy in sync */
526 c->playlist.version = mpd_status_get_queue_version(status);
528 /* remove the song from the local playlist */
529 while (end > start) {
530 --end;
532 /* remove references to the song */
533 if (c->song == playlist_get(&c->playlist, end))
534 c->song = NULL;
536 playlist_remove(&c->playlist, end);
537 }
538 }
540 return true;
541 }
543 bool
544 mpdclient_cmd_move(struct mpdclient *c, unsigned dest_pos, unsigned src_pos)
545 {
546 if (dest_pos == src_pos)
547 return true;
549 struct mpd_connection *connection = mpdclient_get_connection(c);
550 if (connection == NULL)
551 return false;
553 /* send the "move" command to MPD; at the same time, get the
554 new status (to verify the playlist id) */
556 if (!mpd_command_list_begin(connection, false) ||
557 !mpd_send_move(connection, src_pos, dest_pos) ||
558 !mpd_send_status(connection) ||
559 !mpd_command_list_end(connection))
560 return mpdclient_handle_error(c);
562 c->events |= MPD_IDLE_QUEUE;
564 struct mpd_status *status = mpdclient_recv_status(c);
565 if (status == NULL)
566 return false;
568 if (!mpd_response_finish(connection))
569 return mpdclient_handle_error(c);
571 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
572 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
573 /* the cheap route: match on the new playlist length
574 and its version, we can keep our local playlist
575 copy in sync */
576 c->playlist.version = mpd_status_get_queue_version(status);
578 /* swap songs in the local playlist */
579 playlist_move(&c->playlist, dest_pos, src_pos);
580 }
582 return true;
583 }
585 /* The client-to-client protocol (MPD 0.17.0) */
587 bool
588 mpdclient_cmd_subscribe(struct mpdclient *c, const char *channel)
589 {
590 struct mpd_connection *connection = mpdclient_get_connection(c);
592 if (connection == NULL)
593 return false;
595 if (!mpd_send_subscribe(connection, channel))
596 return mpdclient_handle_error(c);
598 return mpdclient_finish_command(c);
599 }
601 bool
602 mpdclient_cmd_unsubscribe(struct mpdclient *c, const char *channel)
603 {
604 struct mpd_connection *connection = mpdclient_get_connection(c);
605 if (connection == NULL)
606 return false;
608 if (!mpd_send_unsubscribe(connection, channel))
609 return mpdclient_handle_error(c);
611 return mpdclient_finish_command(c);
612 }
614 bool
615 mpdclient_cmd_send_message(struct mpdclient *c, const char *channel,
616 const char *text)
617 {
618 struct mpd_connection *connection = mpdclient_get_connection(c);
619 if (connection == NULL)
620 return false;
622 if (!mpd_send_send_message(connection, channel, text))
623 return mpdclient_handle_error(c);
625 return mpdclient_finish_command(c);
626 }
628 bool
629 mpdclient_send_read_messages(struct mpdclient *c)
630 {
631 struct mpd_connection *connection = mpdclient_get_connection(c);
632 if (connection == NULL)
633 return false;
635 return mpd_send_read_messages(connection)?
636 true : mpdclient_handle_error(c);
637 }
639 struct mpd_message *
640 mpdclient_recv_message(struct mpdclient *c)
641 {
642 struct mpd_connection *connection = mpdclient_get_connection(c);
643 if (connection == NULL)
644 return false;
646 struct mpd_message *message = mpd_recv_message(connection);
647 if (message == NULL &&
648 mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS)
649 mpdclient_handle_error(c);
651 return message;
652 }
654 /****************************************************************************/
655 /*** Playlist management functions ******************************************/
656 /****************************************************************************/
658 /* update playlist */
659 bool
660 mpdclient_playlist_update(struct mpdclient *c)
661 {
662 struct mpd_connection *connection = mpdclient_get_connection(c);
663 if (connection == NULL)
664 return false;
666 playlist_clear(&c->playlist);
668 mpd_send_list_queue_meta(connection);
670 struct mpd_entity *entity;
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);
676 }
678 c->playlist.version = mpd_status_get_queue_version(c->status);
679 c->song = NULL;
681 return mpdclient_finish_command(c);
682 }
684 /* update playlist (plchanges) */
685 bool
686 mpdclient_playlist_update_changes(struct mpdclient *c)
687 {
688 struct mpd_connection *connection = mpdclient_get_connection(c);
690 if (connection == NULL)
691 return false;
693 mpd_send_queue_changes_meta(connection, c->playlist.version);
695 struct mpd_song *song;
696 while ((song = mpd_recv_song(connection)) != NULL) {
697 int pos = mpd_song_get_pos(song);
699 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
700 /* update song */
701 playlist_replace(&c->playlist, pos, song);
702 } else {
703 /* add a new song */
704 playlist_append(&c->playlist, song);
705 }
707 mpd_song_free(song);
708 }
710 /* remove trailing songs */
712 unsigned length = mpd_status_get_queue_length(c->status);
713 while (length < c->playlist.list->len) {
714 guint pos = c->playlist.list->len - 1;
716 /* Remove the last playlist entry */
717 playlist_remove(&c->playlist, pos);
718 }
720 c->song = NULL;
721 c->playlist.version = mpd_status_get_queue_version(c->status);
723 return mpdclient_finish_command(c);
724 }
727 /****************************************************************************/
728 /*** Filelist functions *****************************************************/
729 /****************************************************************************/
731 bool
732 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
733 {
734 struct mpd_connection *connection = mpdclient_get_connection(c);
735 if (connection == NULL)
736 return false;
738 if (filelist_is_empty(fl))
739 return true;
741 mpd_command_list_begin(connection, false);
743 for (unsigned i = 0; i < filelist_length(fl); ++i) {
744 struct filelist_entry *entry = filelist_get(fl, i);
745 struct mpd_entity *entity = entry->entity;
747 if (entity != NULL &&
748 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
749 const struct mpd_song *song =
750 mpd_entity_get_song(entity);
752 mpd_send_add(connection, mpd_song_get_uri(song));
753 }
754 }
756 mpd_command_list_end(connection);
757 return mpdclient_finish_command(c);
758 }