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 "filelist.h"
22 #include "screen_client.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 screen_auth(c))
77 return true;
79 mpdclient_ui_error(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->connection_id;
167 return true;
168 }
170 bool
171 mpdclient_update(struct mpdclient *c)
172 {
173 struct mpd_connection *connection = mpdclient_get_connection(c);
175 c->volume = -1;
177 if (connection == NULL)
178 return false;
180 /* free the old status */
181 if (c->status)
182 mpd_status_free(c->status);
184 /* retrieve new status */
185 c->status = mpd_run_status(connection);
186 if (c->status == NULL)
187 return mpdclient_handle_error(c);
189 c->update_id = mpd_status_get_update_id(c->status);
191 c->volume = mpd_status_get_volume(c->status);
193 /* check if the playlist needs an update */
194 if (c->playlist.version != mpd_status_get_queue_version(c->status)) {
195 bool retval;
197 if (!playlist_is_empty(&c->playlist))
198 retval = mpdclient_playlist_update_changes(c);
199 else
200 retval = mpdclient_playlist_update(c);
201 if (!retval)
202 return false;
203 }
205 /* update the current song */
206 if (!c->song || mpd_status_get_song_id(c->status) >= 0) {
207 c->song = playlist_get_song(&c->playlist,
208 mpd_status_get_song_pos(c->status));
209 }
211 return true;
212 }
214 struct mpd_connection *
215 mpdclient_get_connection(struct mpdclient *c)
216 {
217 if (c->source != NULL && c->idle) {
218 c->idle = false;
219 mpd_glib_leave(c->source);
220 }
222 return c->connection;
223 }
225 void
226 mpdclient_put_connection(struct mpdclient *c)
227 {
228 assert(c->source == NULL || c->connection != NULL);
230 if (c->source != NULL && !c->idle) {
231 c->idle = mpd_glib_enter(c->source);
232 }
233 }
235 static struct mpd_status *
236 mpdclient_recv_status(struct mpdclient *c)
237 {
238 assert(c->connection != NULL);
240 struct mpd_status *status = mpd_recv_status(c->connection);
241 if (status == NULL) {
242 mpdclient_handle_error(c);
243 return NULL;
244 }
246 if (c->status != NULL)
247 mpd_status_free(c->status);
248 return c->status = status;
249 }
251 /****************************************************************************/
252 /*** MPD Commands **********************************************************/
253 /****************************************************************************/
255 bool
256 mpdclient_cmd_crop(struct mpdclient *c)
257 {
258 if (!mpdclient_is_playing(c))
259 return false;
261 int length = mpd_status_get_queue_length(c->status);
262 int current = mpd_status_get_song_pos(c->status);
263 if (current < 0 || mpd_status_get_queue_length(c->status) < 2)
264 return true;
266 struct mpd_connection *connection = mpdclient_get_connection(c);
267 if (connection == NULL)
268 return false;
270 mpd_command_list_begin(connection, false);
272 if (current < length - 1)
273 mpd_send_delete_range(connection, current + 1, length);
274 if (current > 0)
275 mpd_send_delete_range(connection, 0, current);
277 mpd_command_list_end(connection);
279 return mpdclient_finish_command(c);
280 }
282 bool
283 mpdclient_cmd_clear(struct mpdclient *c)
284 {
285 struct mpd_connection *connection = mpdclient_get_connection(c);
286 if (connection == NULL)
287 return false;
289 /* send "clear" and "status" */
290 if (!mpd_command_list_begin(connection, false) ||
291 !mpd_send_clear(connection) ||
292 !mpd_send_status(connection) ||
293 !mpd_command_list_end(connection))
294 return mpdclient_handle_error(c);
296 /* receive the new status, store it in the mpdclient struct */
298 struct mpd_status *status = mpdclient_recv_status(c);
299 if (status == NULL)
300 return false;
302 if (!mpd_response_finish(connection))
303 return mpdclient_handle_error(c);
305 /* update mpdclient.playlist */
307 if (mpd_status_get_queue_length(status) == 0) {
308 /* after the "clear" command, the queue is really
309 empty - this means we can clear it locally,
310 reducing the UI latency */
311 playlist_clear(&c->playlist);
312 c->playlist.version = mpd_status_get_queue_version(status);
313 c->song = NULL;
314 }
316 c->events |= MPD_IDLE_QUEUE;
317 return true;
318 }
320 bool
321 mpdclient_cmd_volume(struct mpdclient *c, gint value)
322 {
323 struct mpd_connection *connection = mpdclient_get_connection(c);
324 if (connection == NULL)
325 return false;
327 mpd_send_set_volume(connection, value);
328 return mpdclient_finish_command(c);
329 }
331 bool
332 mpdclient_cmd_volume_up(struct mpdclient *c)
333 {
334 struct mpd_connection *connection = mpdclient_get_connection(c);
335 if (connection == NULL)
336 return false;
338 if (c->status == NULL ||
339 mpd_status_get_volume(c->status) == -1)
340 return true;
342 if (c->volume < 0)
343 c->volume = mpd_status_get_volume(c->status);
345 if (c->volume >= 100)
346 return true;
348 return mpdclient_cmd_volume(c, ++c->volume);
349 }
351 bool
352 mpdclient_cmd_volume_down(struct mpdclient *c)
353 {
354 struct mpd_connection *connection = mpdclient_get_connection(c);
355 if (connection == NULL)
356 return false;
358 if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
359 return true;
361 if (c->volume < 0)
362 c->volume = mpd_status_get_volume(c->status);
364 if (c->volume <= 0)
365 return true;
367 return mpdclient_cmd_volume(c, --c->volume);
368 }
370 bool
371 mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
372 {
373 struct mpd_connection *connection = mpdclient_get_connection(c);
374 if (connection == NULL)
375 return false;
377 return mpd_send_add(connection, path_utf8)?
378 mpdclient_finish_command(c) : false;
379 }
381 bool
382 mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
383 {
384 assert(c != NULL);
385 assert(song != NULL);
387 struct mpd_connection *connection = mpdclient_get_connection(c);
388 if (connection == NULL || c->status == NULL)
389 return false;
391 /* send the add command to mpd; at the same time, get the new
392 status (to verify the new playlist id) and the last song
393 (we hope that's the song we just added) */
395 if (!mpd_command_list_begin(connection, true) ||
396 !mpd_send_add(connection, mpd_song_get_uri(song)) ||
397 !mpd_send_status(connection) ||
398 !mpd_send_get_queue_song_pos(connection,
399 playlist_length(&c->playlist)) ||
400 !mpd_command_list_end(connection) ||
401 !mpd_response_next(connection))
402 return mpdclient_handle_error(c);
404 c->events |= MPD_IDLE_QUEUE;
406 struct mpd_status *status = mpdclient_recv_status(c);
407 if (status == NULL)
408 return false;
410 if (!mpd_response_next(connection))
411 return mpdclient_handle_error(c);
413 struct mpd_song *new_song = mpd_recv_song(connection);
414 if (!mpd_response_finish(connection) || new_song == NULL) {
415 if (new_song != NULL)
416 mpd_song_free(new_song);
418 return mpd_connection_clear_error(connection) ||
419 mpdclient_handle_error(c);
420 }
422 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 &&
423 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
424 /* the cheap route: match on the new playlist length
425 and its version, we can keep our local playlist
426 copy in sync */
427 c->playlist.version = mpd_status_get_queue_version(status);
429 /* the song we just received has the correct id;
430 append it to the local playlist */
431 playlist_append(&c->playlist, new_song);
432 }
434 mpd_song_free(new_song);
436 return true;
437 }
439 bool
440 mpdclient_cmd_delete(struct mpdclient *c, gint idx)
441 {
442 struct mpd_connection *connection = mpdclient_get_connection(c);
444 if (connection == NULL || c->status == NULL)
445 return false;
447 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
448 return false;
450 const struct mpd_song *song = playlist_get(&c->playlist, idx);
452 /* send the delete command to mpd; at the same time, get the
453 new status (to verify the playlist id) */
455 if (!mpd_command_list_begin(connection, false) ||
456 !mpd_send_delete_id(connection, mpd_song_get_id(song)) ||
457 !mpd_send_status(connection) ||
458 !mpd_command_list_end(connection))
459 return mpdclient_handle_error(c);
461 c->events |= MPD_IDLE_QUEUE;
463 struct mpd_status *status = mpdclient_recv_status(c);
464 if (status == NULL)
465 return false;
467 if (!mpd_response_finish(connection))
468 return mpdclient_handle_error(c);
470 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
471 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
472 /* the cheap route: match on the new playlist length
473 and its version, we can keep our local playlist
474 copy in sync */
475 c->playlist.version = mpd_status_get_queue_version(status);
477 /* remove the song from the local playlist */
478 playlist_remove(&c->playlist, idx);
480 /* remove references to the song */
481 if (c->song == song)
482 c->song = NULL;
483 }
485 return true;
486 }
488 bool
489 mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end)
490 {
491 if (end == start + 1)
492 /* if that's not really a range, we choose to use the
493 safer "deleteid" version */
494 return mpdclient_cmd_delete(c, start);
496 struct mpd_connection *connection = mpdclient_get_connection(c);
497 if (connection == NULL)
498 return false;
500 /* send the delete command to mpd; at the same time, get the
501 new status (to verify the playlist id) */
503 if (!mpd_command_list_begin(connection, false) ||
504 !mpd_send_delete_range(connection, start, end) ||
505 !mpd_send_status(connection) ||
506 !mpd_command_list_end(connection))
507 return mpdclient_handle_error(c);
509 c->events |= MPD_IDLE_QUEUE;
511 struct mpd_status *status = mpdclient_recv_status(c);
512 if (status == NULL)
513 return false;
515 if (!mpd_response_finish(connection))
516 return mpdclient_handle_error(c);
518 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) &&
519 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
520 /* the cheap route: match on the new playlist length
521 and its version, we can keep our local playlist
522 copy in sync */
523 c->playlist.version = mpd_status_get_queue_version(status);
525 /* remove the song from the local playlist */
526 while (end > start) {
527 --end;
529 /* remove references to the song */
530 if (c->song == playlist_get(&c->playlist, end))
531 c->song = NULL;
533 playlist_remove(&c->playlist, end);
534 }
535 }
537 return true;
538 }
540 bool
541 mpdclient_cmd_move(struct mpdclient *c, unsigned dest_pos, unsigned src_pos)
542 {
543 if (dest_pos == src_pos)
544 return true;
546 struct mpd_connection *connection = mpdclient_get_connection(c);
547 if (connection == NULL)
548 return false;
550 /* send the "move" command to MPD; at the same time, get the
551 new status (to verify the playlist id) */
553 if (!mpd_command_list_begin(connection, false) ||
554 !mpd_send_move(connection, src_pos, dest_pos) ||
555 !mpd_send_status(connection) ||
556 !mpd_command_list_end(connection))
557 return mpdclient_handle_error(c);
559 c->events |= MPD_IDLE_QUEUE;
561 struct mpd_status *status = mpdclient_recv_status(c);
562 if (status == NULL)
563 return false;
565 if (!mpd_response_finish(connection))
566 return mpdclient_handle_error(c);
568 if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) &&
569 mpd_status_get_queue_version(status) == c->playlist.version + 1) {
570 /* the cheap route: match on the new playlist length
571 and its version, we can keep our local playlist
572 copy in sync */
573 c->playlist.version = mpd_status_get_queue_version(status);
575 /* swap songs in the local playlist */
576 playlist_move(&c->playlist, dest_pos, src_pos);
577 }
579 return true;
580 }
582 /* The client-to-client protocol (MPD 0.17.0) */
584 bool
585 mpdclient_cmd_subscribe(struct mpdclient *c, const char *channel)
586 {
587 struct mpd_connection *connection = mpdclient_get_connection(c);
589 if (connection == NULL)
590 return false;
592 if (!mpd_send_subscribe(connection, channel))
593 return mpdclient_handle_error(c);
595 return mpdclient_finish_command(c);
596 }
598 bool
599 mpdclient_cmd_unsubscribe(struct mpdclient *c, const char *channel)
600 {
601 struct mpd_connection *connection = mpdclient_get_connection(c);
602 if (connection == NULL)
603 return false;
605 if (!mpd_send_unsubscribe(connection, channel))
606 return mpdclient_handle_error(c);
608 return mpdclient_finish_command(c);
609 }
611 bool
612 mpdclient_cmd_send_message(struct mpdclient *c, const char *channel,
613 const char *text)
614 {
615 struct mpd_connection *connection = mpdclient_get_connection(c);
616 if (connection == NULL)
617 return false;
619 if (!mpd_send_send_message(connection, channel, text))
620 return mpdclient_handle_error(c);
622 return mpdclient_finish_command(c);
623 }
625 bool
626 mpdclient_send_read_messages(struct mpdclient *c)
627 {
628 struct mpd_connection *connection = mpdclient_get_connection(c);
629 if (connection == NULL)
630 return false;
632 return mpd_send_read_messages(connection)?
633 true : mpdclient_handle_error(c);
634 }
636 struct mpd_message *
637 mpdclient_recv_message(struct mpdclient *c)
638 {
639 struct mpd_connection *connection = mpdclient_get_connection(c);
640 if (connection == NULL)
641 return false;
643 struct mpd_message *message = mpd_recv_message(connection);
644 if (message == NULL &&
645 mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS)
646 mpdclient_handle_error(c);
648 return message;
649 }
651 /****************************************************************************/
652 /*** Playlist management functions ******************************************/
653 /****************************************************************************/
655 /* update playlist */
656 bool
657 mpdclient_playlist_update(struct mpdclient *c)
658 {
659 struct mpd_connection *connection = mpdclient_get_connection(c);
660 if (connection == NULL)
661 return false;
663 playlist_clear(&c->playlist);
665 mpd_send_list_queue_meta(connection);
667 struct mpd_entity *entity;
668 while ((entity = mpd_recv_entity(connection))) {
669 if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG)
670 playlist_append(&c->playlist, mpd_entity_get_song(entity));
672 mpd_entity_free(entity);
673 }
675 c->playlist.version = mpd_status_get_queue_version(c->status);
676 c->song = NULL;
678 return mpdclient_finish_command(c);
679 }
681 /* update playlist (plchanges) */
682 bool
683 mpdclient_playlist_update_changes(struct mpdclient *c)
684 {
685 struct mpd_connection *connection = mpdclient_get_connection(c);
687 if (connection == NULL)
688 return false;
690 mpd_send_queue_changes_meta(connection, c->playlist.version);
692 struct mpd_song *song;
693 while ((song = mpd_recv_song(connection)) != NULL) {
694 int pos = mpd_song_get_pos(song);
696 if (pos >= 0 && (guint)pos < c->playlist.list->len) {
697 /* update song */
698 playlist_replace(&c->playlist, pos, song);
699 } else {
700 /* add a new song */
701 playlist_append(&c->playlist, song);
702 }
704 mpd_song_free(song);
705 }
707 /* remove trailing songs */
709 unsigned length = mpd_status_get_queue_length(c->status);
710 while (length < c->playlist.list->len) {
711 guint pos = c->playlist.list->len - 1;
713 /* Remove the last playlist entry */
714 playlist_remove(&c->playlist, pos);
715 }
717 c->song = NULL;
718 c->playlist.version = mpd_status_get_queue_version(c->status);
720 return mpdclient_finish_command(c);
721 }
724 /****************************************************************************/
725 /*** Filelist functions *****************************************************/
726 /****************************************************************************/
728 bool
729 mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl)
730 {
731 struct mpd_connection *connection = mpdclient_get_connection(c);
732 if (connection == NULL)
733 return false;
735 if (filelist_is_empty(fl))
736 return true;
738 mpd_command_list_begin(connection, false);
740 for (unsigned i = 0; i < filelist_length(fl); ++i) {
741 struct filelist_entry *entry = filelist_get(fl, i);
742 struct mpd_entity *entity = entry->entity;
744 if (entity != NULL &&
745 mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) {
746 const struct mpd_song *song =
747 mpd_entity_get_song(entity);
749 mpd_send_add(connection, mpd_song_get_uri(song));
750 }
751 }
753 mpd_command_list_end(connection);
754 return mpdclient_finish_command(c);
755 }