1 /* ncmpc (Ncurses MPD Client)
2 * (c) 2004-2009 The Music Player Daemon Project
3 * Project homepage: http://musicpd.org
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.
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.
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 "screen_utils.h"
22 #include "config.h"
23 #include "options.h"
24 #include "strfsong.h"
25 #include "utils.h"
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <time.h>
30 #include <string.h>
32 #undef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD /* broken with song id's */
33 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
34 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
35 #define ENABLE_SONG_ID
36 #define ENABLE_PLCHANGES
38 #define BUFSIZE 1024
40 #define MPD_ERROR(c) (c==NULL || c->connection==NULL || c->connection->error)
42 /* filelist sorting functions */
43 static gint
44 compare_filelistentry(gconstpointer filelist_entry1,
45 gconstpointer filelist_entry2)
46 {
47 const mpd_InfoEntity *e1, *e2;
48 int n = 0;
50 e1 = ((const filelist_entry_t *)filelist_entry1)->entity;
51 e2 = ((const filelist_entry_t *)filelist_entry2)->entity;
53 if (e1 && e2 && e1->type == e2->type) {
54 switch (e1->type) {
55 case MPD_INFO_ENTITY_TYPE_DIRECTORY:
56 n = g_utf8_collate(e1->info.directory->path,
57 e2->info.directory->path);
58 break;
59 case MPD_INFO_ENTITY_TYPE_SONG:
60 break;
61 case MPD_INFO_ENTITY_TYPE_PLAYLISTFILE:
62 n = g_utf8_collate(e1->info.playlistFile->path,
63 e2->info.playlistFile->path);
64 }
65 }
66 return n;
67 }
69 /* sort by list-format */
70 gint
71 compare_filelistentry_format(gconstpointer filelist_entry1,
72 gconstpointer filelist_entry2)
73 {
74 const mpd_InfoEntity *e1, *e2;
75 char key1[BUFSIZE], key2[BUFSIZE];
76 int n = 0;
78 e1 = ((const filelist_entry_t *)filelist_entry1)->entity;
79 e2 = ((const filelist_entry_t *)filelist_entry2)->entity;
81 if (e1 && e2 &&
82 e1->type == MPD_INFO_ENTITY_TYPE_SONG &&
83 e2->type == MPD_INFO_ENTITY_TYPE_SONG) {
84 strfsong(key1, BUFSIZE, options.list_format, e1->info.song);
85 strfsong(key2, BUFSIZE, options.list_format, e2->info.song);
86 n = strcmp(key1,key2);
87 }
89 return n;
90 }
93 /* Error callbacks */
94 static gint
95 error_cb(mpdclient_t *c, gint error, const gchar *msg)
96 {
97 GList *list = c->error_callbacks;
99 if (list == NULL)
100 fprintf(stderr, "error [%d]: %s\n", (error & 0xFF), msg);
102 while (list) {
103 mpdc_error_cb_t cb = list->data;
104 if (cb)
105 cb(c, error, msg);
106 list = list->next;
107 }
109 mpd_clearError(c->connection);
110 return error;
111 }
114 /****************************************************************************/
115 /*** mpdclient functions ****************************************************/
116 /****************************************************************************/
118 gint
119 mpdclient_finish_command(mpdclient_t *c)
120 {
121 mpd_finishCommand(c->connection);
123 if (c->connection->error) {
124 gint error = c->connection->error;
126 if (error == MPD_ERROR_ACK &&
127 c->connection->errorCode == MPD_ACK_ERROR_PERMISSION &&
128 screen_auth(c) == 0)
129 return 0;
131 if (error == MPD_ERROR_ACK)
132 error = error | (c->connection->errorCode << 8);
134 error_cb(c, error, c->connection->errorStr);
135 return error;
136 }
138 return 0;
139 }
141 mpdclient_t *
142 mpdclient_new(void)
143 {
144 mpdclient_t *c;
146 c = g_malloc0(sizeof(mpdclient_t));
147 playlist_init(&c->playlist);
149 return c;
150 }
152 void
153 mpdclient_free(mpdclient_t *c)
154 {
155 mpdclient_disconnect(c);
157 mpdclient_playlist_free(&c->playlist);
159 g_list_free(c->error_callbacks);
160 g_list_free(c->playlist_callbacks);
161 g_list_free(c->browse_callbacks);
162 g_free(c);
163 }
165 gint
166 mpdclient_disconnect(mpdclient_t *c)
167 {
168 if (c->connection)
169 mpd_closeConnection(c->connection);
170 c->connection = NULL;
172 if (c->status)
173 mpd_freeStatus(c->status);
174 c->status = NULL;
176 playlist_clear(&c->playlist);
178 if (c->song)
179 c->song = NULL;
181 return 0;
182 }
184 gint
185 mpdclient_connect(mpdclient_t *c,
186 gchar *host,
187 gint port,
188 gfloat _timeout,
189 gchar *password)
190 {
191 gint retval = 0;
193 /* close any open connection */
194 if( c->connection )
195 mpdclient_disconnect(c);
197 /* connect to MPD */
198 c->connection = mpd_newConnection(host, port, _timeout);
199 if( c->connection->error )
200 return error_cb(c, c->connection->error,
201 c->connection->errorStr);
203 /* send password */
204 if( password ) {
205 mpd_sendPasswordCommand(c->connection, password);
206 retval = mpdclient_finish_command(c);
207 }
208 c->need_update = TRUE;
210 return retval;
211 }
213 gint
214 mpdclient_update(mpdclient_t *c)
215 {
216 gint retval = 0;
218 if (MPD_ERROR(c))
219 return -1;
221 /* free the old status */
222 if (c->status)
223 mpd_freeStatus(c->status);
225 /* retrieve new status */
226 mpd_sendStatusCommand(c->connection);
227 c->status = mpd_getStatus(c->connection);
228 if ((retval=mpdclient_finish_command(c)))
229 return retval;
231 /* check if the playlist needs an update */
232 if (c->playlist.id != c->status->playlist) {
233 if (playlist_is_empty(&c->playlist))
234 retval = mpdclient_playlist_update_changes(c);
235 else
236 retval = mpdclient_playlist_update(c);
237 }
239 /* update the current song */
240 if (!c->song || c->status->songid != c->song->id) {
241 c->song = playlist_get_song(c, c->status->song);
242 }
244 c->need_update = FALSE;
246 return retval;
247 }
250 /****************************************************************************/
251 /*** MPD Commands **********************************************************/
252 /****************************************************************************/
254 gint
255 mpdclient_cmd_play(mpdclient_t *c, gint idx)
256 {
257 #ifdef ENABLE_SONG_ID
258 struct mpd_song *song = playlist_get_song(c, idx);
260 if (song)
261 mpd_sendPlayIdCommand(c->connection, song->id);
262 else
263 mpd_sendPlayIdCommand(c->connection, MPD_PLAY_AT_BEGINNING);
264 #else
265 mpd_sendPlayCommand(c->connection, idx);
266 #endif
267 c->need_update = TRUE;
268 return mpdclient_finish_command(c);
269 }
271 gint
272 mpdclient_cmd_pause(mpdclient_t *c, gint value)
273 {
274 mpd_sendPauseCommand(c->connection, value);
275 return mpdclient_finish_command(c);
276 }
278 gint
279 mpdclient_cmd_crop(mpdclient_t *c)
280 {
281 mpd_Status *status;
282 int length;
284 mpd_sendStatusCommand(c->connection);
285 status = mpd_getStatus(c->connection);
286 length = status->playlistLength - 1;
288 if (length <= 0) {
289 mpd_freeStatus(status);
290 } else if (status->state == 3 || status->state == 2) {
291 /* If playing or paused */
293 mpd_sendCommandListBegin( c->connection );
295 while (length >= 0) {
296 if (length != status->song)
297 mpd_sendDeleteCommand(c->connection, length);
299 length--;
300 }
302 mpd_sendCommandListEnd(c->connection);
303 mpd_freeStatus(status);
304 } else {
305 mpd_freeStatus(status);
306 }
308 return mpdclient_finish_command(c);
309 }
311 gint
312 mpdclient_cmd_stop(mpdclient_t *c)
313 {
314 mpd_sendStopCommand(c->connection);
315 return mpdclient_finish_command(c);
316 }
318 gint
319 mpdclient_cmd_next(mpdclient_t *c)
320 {
321 mpd_sendNextCommand(c->connection);
322 c->need_update = TRUE;
323 return mpdclient_finish_command(c);
324 }
326 gint
327 mpdclient_cmd_prev(mpdclient_t *c)
328 {
329 mpd_sendPrevCommand(c->connection);
330 c->need_update = TRUE;
331 return mpdclient_finish_command(c);
332 }
334 gint
335 mpdclient_cmd_seek(mpdclient_t *c, gint id, gint pos)
336 {
337 mpd_sendSeekIdCommand(c->connection, id, pos);
338 return mpdclient_finish_command(c);
339 }
341 gint
342 mpdclient_cmd_shuffle(mpdclient_t *c)
343 {
344 mpd_sendShuffleCommand(c->connection);
345 c->need_update = TRUE;
346 return mpdclient_finish_command(c);
347 }
349 gint
350 mpdclient_cmd_shuffle_range(mpdclient_t *c, guint start, guint end)
351 {
352 mpd_sendShuffleRangeCommand(c->connection, start, end);
353 c->need_update = TRUE;
354 return mpdclient_finish_command(c);
355 }
357 gint
358 mpdclient_cmd_clear(mpdclient_t *c)
359 {
360 gint retval = 0;
362 mpd_sendClearCommand(c->connection);
363 retval = mpdclient_finish_command(c);
364 /* call playlist updated callback */
365 mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL);
366 c->need_update = TRUE;
367 return retval;
368 }
370 gint
371 mpdclient_cmd_repeat(mpdclient_t *c, gint value)
372 {
373 mpd_sendRepeatCommand(c->connection, value);
374 return mpdclient_finish_command(c);
375 }
377 gint
378 mpdclient_cmd_random(mpdclient_t *c, gint value)
379 {
380 mpd_sendRandomCommand(c->connection, value);
381 return mpdclient_finish_command(c);
382 }
384 gint
385 mpdclient_cmd_single(mpdclient_t *c, gint value)
386 {
387 mpd_sendSingleCommand(c->connection, value);
388 return mpdclient_finish_command(c);
389 }
391 gint
392 mpdclient_cmd_consume(mpdclient_t *c, gint value)
393 {
394 mpd_sendConsumeCommand(c->connection, value);
395 return mpdclient_finish_command(c);
396 }
398 gint
399 mpdclient_cmd_crossfade(mpdclient_t *c, gint value)
400 {
401 mpd_sendCrossfadeCommand(c->connection, value);
402 return mpdclient_finish_command(c);
403 }
405 gint
406 mpdclient_cmd_db_update(mpdclient_t *c, gchar *path)
407 {
408 mpd_sendUpdateCommand(c->connection, path ? path : "");
409 return mpdclient_finish_command(c);
410 }
412 gint
413 mpdclient_cmd_volume(mpdclient_t *c, gint value)
414 {
415 mpd_sendSetvolCommand(c->connection, value);
416 return mpdclient_finish_command(c);
417 }
419 gint
420 mpdclient_cmd_add_path(mpdclient_t *c, gchar *path_utf8)
421 {
422 mpd_sendAddCommand(c->connection, path_utf8);
423 return mpdclient_finish_command(c);
424 }
426 gint
427 mpdclient_cmd_add(mpdclient_t *c, struct mpd_song *song)
428 {
429 gint retval = 0;
431 if( !song || !song->file )
432 return -1;
434 /* send the add command to mpd */
435 mpd_sendAddCommand(c->connection, song->file);
436 if( (retval=mpdclient_finish_command(c)) )
437 return retval;
439 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
440 /* add the song to playlist */
441 playlist_append(&c->playlist, song);
443 /* increment the playlist id, so we don't retrieve a new playlist */
444 c->playlist.id++;
446 /* call playlist updated callback */
447 mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
448 #else
449 c->need_update = TRUE;
450 #endif
452 return 0;
453 }
455 gint
456 mpdclient_cmd_delete(mpdclient_t *c, gint idx)
457 {
458 gint retval = 0;
459 struct mpd_song *song;
461 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
462 return -1;
464 song = playlist_get(&c->playlist, idx);
466 /* send the delete command to mpd */
467 #ifdef ENABLE_SONG_ID
468 mpd_sendDeleteIdCommand(c->connection, song->id);
469 #else
470 mpd_sendDeleteCommand(c->connection, idx);
471 #endif
472 if( (retval=mpdclient_finish_command(c)) )
473 return retval;
475 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
476 /* increment the playlist id, so we don't retrieve a new playlist */
477 c->playlist.id++;
479 /* remove the song from the playlist */
480 playlist_remove_reuse(&c->playlist, idx);
482 /* call playlist updated callback */
483 mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song);
485 /* remove references to the song */
486 if (c->song == song) {
487 c->song = NULL;
488 c->need_update = TRUE;
489 }
491 mpd_freeSong(song);
493 #else
494 c->need_update = TRUE;
495 #endif
497 return 0;
498 }
500 gint
501 mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index)
502 {
503 gint n;
504 struct mpd_song *song1, *song2;
506 if (old_index == new_index || new_index < 0 ||
507 (guint)new_index >= c->playlist.list->len)
508 return -1;
510 song1 = playlist_get(&c->playlist, old_index);
511 song2 = playlist_get(&c->playlist, new_index);
513 /* send the move command to mpd */
514 #ifdef ENABLE_SONG_ID
515 mpd_sendSwapIdCommand(c->connection, song1->id, song2->id);
516 #else
517 mpd_sendMoveCommand(c->connection, old_index, new_index);
518 #endif
519 if( (n=mpdclient_finish_command(c)) )
520 return n;
522 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
523 /* update the playlist */
524 playlist_swap(&c->playlist, old_index, new_index);
526 /* increment the playlist id, so we don't retrieve a new playlist */
527 c->playlist.id++;
529 #else
530 c->need_update = TRUE;
531 #endif
533 /* call playlist updated callback */
534 mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index);
536 return 0;
537 }
539 gint
540 mpdclient_cmd_save_playlist(mpdclient_t *c, gchar *filename_utf8)
541 {
542 gint retval = 0;
544 mpd_sendSaveCommand(c->connection, filename_utf8);
545 if ((retval = mpdclient_finish_command(c)) == 0)
546 mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL);
547 return retval;
548 }
550 gint
551 mpdclient_cmd_load_playlist(mpdclient_t *c, gchar *filename_utf8)
552 {
553 mpd_sendLoadCommand(c->connection, filename_utf8);
554 c->need_update = TRUE;
555 return mpdclient_finish_command(c);
556 }
558 gint
559 mpdclient_cmd_delete_playlist(mpdclient_t *c, gchar *filename_utf8)
560 {
561 gint retval = 0;
563 mpd_sendRmCommand(c->connection, filename_utf8);
564 if ((retval = mpdclient_finish_command(c)) == 0)
565 mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL);
566 return retval;
567 }
570 /****************************************************************************/
571 /*** Callback management functions ******************************************/
572 /****************************************************************************/
574 static void
575 do_list_callbacks(mpdclient_t *c, GList *list, gint event, gpointer data)
576 {
577 while (list) {
578 mpdc_list_cb_t fn = list->data;
580 fn(c, event, data);
581 list = list->next;
582 }
583 }
585 void
586 mpdclient_playlist_callback(mpdclient_t *c, int event, gpointer data)
587 {
588 do_list_callbacks(c, c->playlist_callbacks, event, data);
589 }
591 void
592 mpdclient_install_playlist_callback(mpdclient_t *c,mpdc_list_cb_t cb)
593 {
594 c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb);
595 }
597 void
598 mpdclient_remove_playlist_callback(mpdclient_t *c, mpdc_list_cb_t cb)
599 {
600 c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb);
601 }
603 void
604 mpdclient_browse_callback(mpdclient_t *c, int event, gpointer data)
605 {
606 do_list_callbacks(c, c->browse_callbacks, event, data);
607 }
610 void
611 mpdclient_install_browse_callback(mpdclient_t *c,mpdc_list_cb_t cb)
612 {
613 c->browse_callbacks = g_list_append(c->browse_callbacks, cb);
614 }
616 void
617 mpdclient_remove_browse_callback(mpdclient_t *c, mpdc_list_cb_t cb)
618 {
619 c->browse_callbacks = g_list_remove(c->browse_callbacks, cb);
620 }
622 void
623 mpdclient_install_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
624 {
625 c->error_callbacks = g_list_append(c->error_callbacks, cb);
626 }
628 void
629 mpdclient_remove_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
630 {
631 c->error_callbacks = g_list_remove(c->error_callbacks, cb);
632 }
635 /****************************************************************************/
636 /*** Playlist management functions ******************************************/
637 /****************************************************************************/
639 /* update playlist */
640 gint
641 mpdclient_playlist_update(mpdclient_t *c)
642 {
643 mpd_InfoEntity *entity;
645 if (MPD_ERROR(c))
646 return -1;
648 playlist_clear(&c->playlist);
650 mpd_sendPlaylistInfoCommand(c->connection,-1);
651 while ((entity = mpd_getNextInfoEntity(c->connection))) {
652 if (entity->type == MPD_INFO_ENTITY_TYPE_SONG)
653 playlist_append(&c->playlist, entity->info.song);
655 mpd_freeInfoEntity(entity);
656 }
658 c->playlist.id = c->status->playlist;
659 c->song = NULL;
661 /* call playlist updated callbacks */
662 mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
664 return mpdclient_finish_command(c);
665 }
667 #ifdef ENABLE_PLCHANGES
669 /* update playlist (plchanges) */
670 gint
671 mpdclient_playlist_update_changes(mpdclient_t *c)
672 {
673 mpd_InfoEntity *entity;
675 if (MPD_ERROR(c))
676 return -1;
678 mpd_sendPlChangesCommand(c->connection, c->playlist.id);
680 while ((entity = mpd_getNextInfoEntity(c->connection)) != NULL) {
681 struct mpd_song *song = entity->info.song;
683 if (song->pos >= 0 && (guint)song->pos < c->playlist.list->len) {
684 /* update song */
685 playlist_replace(&c->playlist, song->pos, song);
686 } else {
687 /* add a new song */
688 playlist_append(&c->playlist, song);
689 }
691 mpd_freeInfoEntity(entity);
692 }
694 /* remove trailing songs */
695 while ((guint)c->status->playlistLength < c->playlist.list->len) {
696 guint pos = c->playlist.list->len - 1;
698 /* Remove the last playlist entry */
699 playlist_remove(&c->playlist, pos);
700 }
702 c->song = NULL;
703 c->playlist.id = c->status->playlist;
705 mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
707 return 0;
708 }
710 #else
711 gint
712 mpdclient_playlist_update_changes(mpdclient_t *c)
713 {
714 return mpdclient_playlist_update(c);
715 }
716 #endif
719 /****************************************************************************/
720 /*** Filelist functions *****************************************************/
721 /****************************************************************************/
723 mpdclient_filelist_t *
724 mpdclient_filelist_get(mpdclient_t *c, const gchar *path)
725 {
726 mpdclient_filelist_t *filelist;
727 mpd_InfoEntity *entity;
729 mpd_sendLsInfoCommand(c->connection, path);
730 filelist = filelist_new(path);
731 if (path && path[0] && strcmp(path, "/"))
732 /* add a dummy entry for ./.. */
733 filelist_append(filelist, NULL);
735 while ((entity=mpd_getNextInfoEntity(c->connection))) {
736 filelist_append(filelist, entity);
737 }
739 /* If there's an error, ignore it. We'll return an empty filelist. */
740 mpdclient_finish_command(c);
742 filelist_sort_dir_play(filelist, compare_filelistentry);
744 return filelist;
745 }
747 mpdclient_filelist_t *
748 mpdclient_filelist_search(mpdclient_t *c,
749 int exact_match,
750 int table,
751 gchar *filter_utf8)
752 {
753 mpdclient_filelist_t *filelist;
754 mpd_InfoEntity *entity;
756 if (exact_match)
757 mpd_sendFindCommand(c->connection, table, filter_utf8);
758 else
759 mpd_sendSearchCommand(c->connection, table, filter_utf8);
760 filelist = filelist_new(NULL);
762 while ((entity=mpd_getNextInfoEntity(c->connection)))
763 filelist_append(filelist, entity);
765 if (mpdclient_finish_command(c)) {
766 filelist_free(filelist);
767 return NULL;
768 }
770 return filelist;
771 }
773 mpdclient_filelist_t *
774 mpdclient_filelist_update(mpdclient_t *c, mpdclient_filelist_t *filelist)
775 {
776 if (filelist != NULL) {
777 gchar *path = g_strdup(filelist->path);
779 filelist_free(filelist);
780 filelist = mpdclient_filelist_get(c, path);
781 g_free(path);
782 return filelist;
783 }
784 return NULL;
785 }
787 int
788 mpdclient_filelist_add_all(mpdclient_t *c, mpdclient_filelist_t *fl)
789 {
790 guint i;
792 if (filelist_is_empty(fl))
793 return 0;
795 mpd_sendCommandListBegin(c->connection);
797 for (i = 0; i < filelist_length(fl); ++i) {
798 filelist_entry_t *entry = filelist_get(fl, i);
799 mpd_InfoEntity *entity = entry->entity;
801 if (entity && entity->type == MPD_INFO_ENTITY_TYPE_SONG) {
802 struct mpd_song *song = entity->info.song;
804 mpd_sendAddCommand(c->connection, song->file);
805 }
806 }
808 mpd_sendCommandListEnd(c->connection);
809 return mpdclient_finish_command(c);
810 }
812 GList *
813 mpdclient_get_artists(mpdclient_t *c)
814 {
815 gchar *str = NULL;
816 GList *list = NULL;
818 mpd_sendListCommand(c->connection, MPD_TABLE_ARTIST, NULL);
819 while ((str = mpd_getNextArtist(c->connection)))
820 list = g_list_append(list, (gpointer) str);
822 if (mpdclient_finish_command(c))
823 return string_list_free(list);
825 return list;
826 }
828 GList *
829 mpdclient_get_albums(mpdclient_t *c, gchar *artist_utf8)
830 {
831 gchar *str = NULL;
832 GList *list = NULL;
834 mpd_sendListCommand(c->connection, MPD_TABLE_ALBUM, artist_utf8);
835 while ((str = mpd_getNextAlbum(c->connection)))
836 list = g_list_append(list, (gpointer) str);
838 if (mpdclient_finish_command(c))
839 return string_list_free(list);
841 return list;
842 }