7907eead5d293862265a95b2bfea884d40b70036
1 /*
2 * (c) 2004 by Kalle Wallin <kaw@linux.se>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 *
17 */
19 #include "mpdclient.h"
20 #include "screen_utils.h"
21 #include "config.h"
22 #include "options.h"
23 #include "strfsong.h"
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <time.h>
28 #include <string.h>
30 #undef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD /* broken with song id's */
31 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
32 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
33 #define ENABLE_SONG_ID
34 #define ENABLE_PLCHANGES
36 #define BUFSIZE 1024
38 #define MPD_ERROR(c) (c==NULL || c->connection==NULL || c->connection->error)
40 /* from utils.c */
41 extern GList *string_list_free(GList *string_list);
44 /* filelist sorting functions */
45 static gint
46 compare_filelistentry_dir(gconstpointer filelist_entry1,
47 gconstpointer filelist_entry2)
48 {
49 const mpd_InfoEntity *e1, *e2;
50 char *key1, *key2;
51 int n = 0;
53 e1 = ((const filelist_entry_t *)filelist_entry1)->entity;
54 e2 = ((const filelist_entry_t *)filelist_entry2)->entity;
56 if (e1 && e2 &&
57 e1->type == MPD_INFO_ENTITY_TYPE_DIRECTORY &&
58 e2->type == MPD_INFO_ENTITY_TYPE_DIRECTORY) {
59 key1 = g_utf8_collate_key(e1->info.directory->path,-1);
60 key2 = g_utf8_collate_key(e2->info.directory->path,-1);
61 n = strcmp(key1,key2);
62 g_free(key1);
63 g_free(key2);
64 }
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, 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 /* retreive 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_clear(mpdclient_t *c)
351 {
352 gint retval = 0;
354 mpd_sendClearCommand(c->connection);
355 retval = mpdclient_finish_command(c);
356 /* call playlist updated callback */
357 mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL);
358 c->need_update = TRUE;
359 return retval;
360 }
362 gint
363 mpdclient_cmd_repeat(mpdclient_t *c, gint value)
364 {
365 mpd_sendRepeatCommand(c->connection, value);
366 return mpdclient_finish_command(c);
367 }
369 gint
370 mpdclient_cmd_random(mpdclient_t *c, gint value)
371 {
372 mpd_sendRandomCommand(c->connection, value);
373 return mpdclient_finish_command(c);
374 }
376 gint
377 mpdclient_cmd_crossfade(mpdclient_t *c, gint value)
378 {
379 mpd_sendCrossfadeCommand(c->connection, value);
380 return mpdclient_finish_command(c);
381 }
383 gint
384 mpdclient_cmd_db_update_utf8(mpdclient_t *c, gchar *path)
385 {
386 mpd_sendUpdateCommand(c->connection, path ? path : "");
387 return mpdclient_finish_command(c);
388 }
390 gint
391 mpdclient_cmd_volume(mpdclient_t *c, gint value)
392 {
393 mpd_sendSetvolCommand(c->connection, value);
394 return mpdclient_finish_command(c);
395 }
397 gint
398 mpdclient_cmd_add_path(mpdclient_t *c, gchar *path_utf8)
399 {
400 mpd_sendAddCommand(c->connection, path_utf8);
401 return mpdclient_finish_command(c);
402 }
404 gint
405 mpdclient_cmd_add(mpdclient_t *c, struct mpd_song *song)
406 {
407 gint retval = 0;
409 if( !song || !song->file )
410 return -1;
412 /* send the add command to mpd */
413 mpd_sendAddCommand(c->connection, song->file);
414 if( (retval=mpdclient_finish_command(c)) )
415 return retval;
417 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
418 /* add the song to playlist */
419 playlist_append(&c->playlist, song);
421 /* increment the playlist id, so we dont retrives a new playlist */
422 c->playlist.id++;
424 /* call playlist updated callback */
425 mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
426 #else
427 c->need_update = TRUE;
428 #endif
430 return 0;
431 }
433 gint
434 mpdclient_cmd_delete(mpdclient_t *c, gint idx)
435 {
436 gint retval = 0;
437 struct mpd_song *song;
439 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
440 return -1;
442 song = playlist_get(&c->playlist, idx);
444 /* send the delete command to mpd */
445 #ifdef ENABLE_SONG_ID
446 mpd_sendDeleteIdCommand(c->connection, song->id);
447 #else
448 mpd_sendDeleteCommand(c->connection, idx);
449 #endif
450 if( (retval=mpdclient_finish_command(c)) )
451 return retval;
453 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
454 /* increment the playlist id, so we dont retrive a new playlist */
455 c->playlist.id++;
457 /* remove the song from the playlist */
458 playlist_remove_reuse(&c->playlist, idx);
460 /* call playlist updated callback */
461 mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song);
463 /* remove references to the song */
464 if (c->song == song) {
465 c->song = NULL;
466 c->need_update = TRUE;
467 }
469 mpd_freeSong(song);
471 #else
472 c->need_update = TRUE;
473 #endif
475 return 0;
476 }
478 gint
479 mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index)
480 {
481 gint n;
482 struct mpd_song *song1, *song2;
484 if (old_index == new_index || new_index < 0 ||
485 (guint)new_index >= c->playlist.list->len)
486 return -1;
488 song1 = playlist_get(&c->playlist, old_index);
489 song2 = playlist_get(&c->playlist, new_index);
491 /* send the move command to mpd */
492 #ifdef ENABLE_SONG_ID
493 mpd_sendSwapIdCommand(c->connection, song1->id, song2->id);
494 #else
495 mpd_sendMoveCommand(c->connection, old_index, new_index);
496 #endif
497 if( (n=mpdclient_finish_command(c)) )
498 return n;
500 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
501 /* update the playlist */
502 playlist_swap(&c->playlist, old_index, new_index);
504 /* increment the playlist id, so we dont retrives a new playlist */
505 c->playlist.id++;
507 #else
508 c->need_update = TRUE;
509 #endif
511 /* call playlist updated callback */
512 mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index);
514 return 0;
515 }
517 gint
518 mpdclient_cmd_save_playlist(mpdclient_t *c, gchar *filename_utf8)
519 {
520 gint retval = 0;
522 mpd_sendSaveCommand(c->connection, filename_utf8);
523 if ((retval = mpdclient_finish_command(c)) == 0)
524 mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL);
525 return retval;
526 }
528 gint
529 mpdclient_cmd_load_playlist_utf8(mpdclient_t *c, gchar *filename_utf8)
530 {
531 mpd_sendLoadCommand(c->connection, filename_utf8);
532 c->need_update = TRUE;
533 return mpdclient_finish_command(c);
534 }
536 gint
537 mpdclient_cmd_delete_playlist(mpdclient_t *c, gchar *filename_utf8)
538 {
539 gint retval = 0;
541 mpd_sendRmCommand(c->connection, filename_utf8);
542 if ((retval = mpdclient_finish_command(c)) == 0)
543 mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL);
544 return retval;
545 }
548 /****************************************************************************/
549 /*** Callback managment functions *******************************************/
550 /****************************************************************************/
552 static void
553 do_list_callbacks(mpdclient_t *c, GList *list, gint event, gpointer data)
554 {
555 while (list) {
556 mpdc_list_cb_t fn = list->data;
558 fn(c, event, data);
559 list = list->next;
560 }
561 }
563 void
564 mpdclient_playlist_callback(mpdclient_t *c, int event, gpointer data)
565 {
566 do_list_callbacks(c, c->playlist_callbacks, event, data);
567 }
569 void
570 mpdclient_install_playlist_callback(mpdclient_t *c,mpdc_list_cb_t cb)
571 {
572 c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb);
573 }
575 void
576 mpdclient_remove_playlist_callback(mpdclient_t *c, mpdc_list_cb_t cb)
577 {
578 c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb);
579 }
581 void
582 mpdclient_browse_callback(mpdclient_t *c, int event, gpointer data)
583 {
584 do_list_callbacks(c, c->browse_callbacks, event, data);
585 }
588 void
589 mpdclient_install_browse_callback(mpdclient_t *c,mpdc_list_cb_t cb)
590 {
591 c->browse_callbacks = g_list_append(c->browse_callbacks, cb);
592 }
594 void
595 mpdclient_remove_browse_callback(mpdclient_t *c, mpdc_list_cb_t cb)
596 {
597 c->browse_callbacks = g_list_remove(c->browse_callbacks, cb);
598 }
600 void
601 mpdclient_install_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
602 {
603 c->error_callbacks = g_list_append(c->error_callbacks, cb);
604 }
606 void
607 mpdclient_remove_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
608 {
609 c->error_callbacks = g_list_remove(c->error_callbacks, cb);
610 }
613 /****************************************************************************/
614 /*** Playlist managment functions *******************************************/
615 /****************************************************************************/
617 /* update playlist */
618 gint
619 mpdclient_playlist_update(mpdclient_t *c)
620 {
621 mpd_InfoEntity *entity;
623 if (MPD_ERROR(c))
624 return -1;
626 playlist_clear(&c->playlist);
628 mpd_sendPlaylistInfoCommand(c->connection,-1);
629 while ((entity = mpd_getNextInfoEntity(c->connection))) {
630 if (entity->type == MPD_INFO_ENTITY_TYPE_SONG)
631 playlist_append(&c->playlist, entity->info.song);
633 mpd_freeInfoEntity(entity);
634 }
636 c->playlist.id = c->status->playlist;
637 c->song = NULL;
639 /* call playlist updated callbacks */
640 mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
642 return mpdclient_finish_command(c);
643 }
645 #ifdef ENABLE_PLCHANGES
647 /* update playlist (plchanges) */
648 gint
649 mpdclient_playlist_update_changes(mpdclient_t *c)
650 {
651 mpd_InfoEntity *entity;
653 if (MPD_ERROR(c))
654 return -1;
656 mpd_sendPlChangesCommand(c->connection, c->playlist.id);
658 while ((entity = mpd_getNextInfoEntity(c->connection)) != NULL) {
659 struct mpd_song *song = entity->info.song;
661 if (song->pos >= 0 && (guint)song->pos < c->playlist.list->len) {
662 /* update song */
663 playlist_replace(&c->playlist, song->pos, song);
664 } else {
665 /* add a new song */
666 playlist_append(&c->playlist, song);
667 }
669 mpd_freeInfoEntity(entity);
670 }
672 /* remove trailing songs */
673 while ((guint)c->status->playlistLength < c->playlist.list->len) {
674 guint pos = c->playlist.list->len - 1;
676 /* Remove the last playlist entry */
677 playlist_remove(&c->playlist, pos);
678 }
680 c->song = NULL;
681 c->playlist.id = c->status->playlist;
683 mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
685 return 0;
686 }
688 #else
689 gint
690 mpdclient_playlist_update_changes(mpdclient_t *c)
691 {
692 return mpdclient_playlist_update(c);
693 }
694 #endif
697 /****************************************************************************/
698 /*** Filelist functions *****************************************************/
699 /****************************************************************************/
701 mpdclient_filelist_t *
702 mpdclient_filelist_get(mpdclient_t *c, const gchar *path)
703 {
704 mpdclient_filelist_t *filelist;
705 mpd_InfoEntity *entity;
706 gboolean has_dirs_only = TRUE;
708 mpd_sendLsInfoCommand(c->connection, path);
709 filelist = filelist_new(path);
710 if (path && path[0] && strcmp(path, "/"))
711 /* add a dummy entry for ./.. */
712 filelist_append(filelist, NULL);
714 while ((entity=mpd_getNextInfoEntity(c->connection))) {
715 filelist_append(filelist, entity);
717 if (has_dirs_only && entity->type != MPD_INFO_ENTITY_TYPE_DIRECTORY) {
718 has_dirs_only = FALSE;
719 }
720 }
722 /* If there's an error, ignore it. We'll return an empty filelist. */
723 mpdclient_finish_command(c);
725 // If there are only directory entities in the filelist, we sort it
726 if (has_dirs_only)
727 filelist_sort(filelist, compare_filelistentry_dir);
729 return filelist;
730 }
732 mpdclient_filelist_t *
733 mpdclient_filelist_search(mpdclient_t *c,
734 int exact_match,
735 int table,
736 gchar *filter_utf8)
737 {
738 mpdclient_filelist_t *filelist;
739 mpd_InfoEntity *entity;
741 if (exact_match)
742 mpd_sendFindCommand(c->connection, table, filter_utf8);
743 else
744 mpd_sendSearchCommand(c->connection, table, filter_utf8);
745 filelist = filelist_new(NULL);
747 while ((entity=mpd_getNextInfoEntity(c->connection)))
748 filelist_append(filelist, entity);
750 if (mpdclient_finish_command(c)) {
751 filelist_free(filelist);
752 return NULL;
753 }
755 return filelist;
756 }
758 mpdclient_filelist_t *
759 mpdclient_filelist_update(mpdclient_t *c, mpdclient_filelist_t *filelist)
760 {
761 if (filelist != NULL) {
762 gchar *path = g_strdup(filelist->path);
764 filelist_free(filelist);
765 filelist = mpdclient_filelist_get(c, path);
766 g_free(path);
767 return filelist;
768 }
769 return NULL;
770 }
772 int
773 mpdclient_filelist_add_all(mpdclient_t *c, mpdclient_filelist_t *fl)
774 {
775 guint i;
777 if (filelist_is_empty(fl))
778 return 0;
780 mpd_sendCommandListBegin(c->connection);
782 for (i = 0; i < filelist_length(fl); ++i) {
783 filelist_entry_t *entry = filelist_get(fl, i);
784 mpd_InfoEntity *entity = entry->entity;
786 if (entity && entity->type == MPD_INFO_ENTITY_TYPE_SONG) {
787 struct mpd_song *song = entity->info.song;
789 mpd_sendAddCommand(c->connection, song->file);
790 }
791 }
793 mpd_sendCommandListEnd(c->connection);
794 return mpdclient_finish_command(c);
795 }
797 GList *
798 mpdclient_get_artists_utf8(mpdclient_t *c)
799 {
800 gchar *str = NULL;
801 GList *list = NULL;
803 mpd_sendListCommand(c->connection, MPD_TABLE_ARTIST, NULL);
804 while ((str = mpd_getNextArtist(c->connection)))
805 list = g_list_append(list, (gpointer) str);
807 if (mpdclient_finish_command(c))
808 return string_list_free(list);
810 return list;
811 }
813 GList *
814 mpdclient_get_albums_utf8(mpdclient_t *c, gchar *artist_utf8)
815 {
816 gchar *str = NULL;
817 GList *list = NULL;
819 mpd_sendListCommand(c->connection, MPD_TABLE_ALBUM, artist_utf8);
820 while ((str = mpd_getNextAlbum(c->connection)))
821 list = g_list_append(list, (gpointer) str);
823 if (mpdclient_finish_command(c))
824 return string_list_free(list);
826 return list;
827 }