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 int n = 0;
52 e1 = ((const filelist_entry_t *)filelist_entry1)->entity;
53 e2 = ((const filelist_entry_t *)filelist_entry2)->entity;
55 if (e1 && e2 &&
56 e1->type == MPD_INFO_ENTITY_TYPE_DIRECTORY &&
57 e2->type == MPD_INFO_ENTITY_TYPE_DIRECTORY)
58 n = g_utf8_collate(e1->info.directory->path,
59 e2->info.directory->path);
61 return n;
62 }
64 /* sort by list-format */
65 gint
66 compare_filelistentry_format(gconstpointer filelist_entry1,
67 gconstpointer filelist_entry2)
68 {
69 const mpd_InfoEntity *e1, *e2;
70 char key1[BUFSIZE], key2[BUFSIZE];
71 int n = 0;
73 e1 = ((const filelist_entry_t *)filelist_entry1)->entity;
74 e2 = ((const filelist_entry_t *)filelist_entry2)->entity;
76 if (e1 && e2 &&
77 e1->type == MPD_INFO_ENTITY_TYPE_SONG &&
78 e2->type == MPD_INFO_ENTITY_TYPE_SONG) {
79 strfsong(key1, BUFSIZE, options.list_format, e1->info.song);
80 strfsong(key2, BUFSIZE, options.list_format, e2->info.song);
81 n = strcmp(key1,key2);
82 }
84 return n;
85 }
88 /* Error callbacks */
89 static gint
90 error_cb(mpdclient_t *c, gint error, gchar *msg)
91 {
92 GList *list = c->error_callbacks;
94 if (list == NULL)
95 fprintf(stderr, "error [%d]: %s\n", (error & 0xFF), msg);
97 while (list) {
98 mpdc_error_cb_t cb = list->data;
99 if (cb)
100 cb(c, error, msg);
101 list = list->next;
102 }
104 mpd_clearError(c->connection);
105 return error;
106 }
109 /****************************************************************************/
110 /*** mpdclient functions ****************************************************/
111 /****************************************************************************/
113 gint
114 mpdclient_finish_command(mpdclient_t *c)
115 {
116 mpd_finishCommand(c->connection);
118 if (c->connection->error) {
119 gint error = c->connection->error;
121 if (error == MPD_ERROR_ACK &&
122 c->connection->errorCode == MPD_ACK_ERROR_PERMISSION &&
123 screen_auth(c) == 0)
124 return 0;
126 if (error == MPD_ERROR_ACK)
127 error = error | (c->connection->errorCode << 8);
129 error_cb(c, error, c->connection->errorStr);
130 return error;
131 }
133 return 0;
134 }
136 mpdclient_t *
137 mpdclient_new(void)
138 {
139 mpdclient_t *c;
141 c = g_malloc0(sizeof(mpdclient_t));
142 playlist_init(&c->playlist);
144 return c;
145 }
147 void
148 mpdclient_free(mpdclient_t *c)
149 {
150 mpdclient_disconnect(c);
152 mpdclient_playlist_free(&c->playlist);
154 g_list_free(c->error_callbacks);
155 g_list_free(c->playlist_callbacks);
156 g_list_free(c->browse_callbacks);
157 g_free(c);
158 }
160 gint
161 mpdclient_disconnect(mpdclient_t *c)
162 {
163 if (c->connection)
164 mpd_closeConnection(c->connection);
165 c->connection = NULL;
167 if (c->status)
168 mpd_freeStatus(c->status);
169 c->status = NULL;
171 playlist_clear(&c->playlist);
173 if (c->song)
174 c->song = NULL;
176 return 0;
177 }
179 gint
180 mpdclient_connect(mpdclient_t *c,
181 gchar *host,
182 gint port,
183 gfloat _timeout,
184 gchar *password)
185 {
186 gint retval = 0;
188 /* close any open connection */
189 if( c->connection )
190 mpdclient_disconnect(c);
192 /* connect to MPD */
193 c->connection = mpd_newConnection(host, port, _timeout);
194 if( c->connection->error )
195 return error_cb(c, c->connection->error,
196 c->connection->errorStr);
198 /* send password */
199 if( password ) {
200 mpd_sendPasswordCommand(c->connection, password);
201 retval = mpdclient_finish_command(c);
202 }
203 c->need_update = TRUE;
205 return retval;
206 }
208 gint
209 mpdclient_update(mpdclient_t *c)
210 {
211 gint retval = 0;
213 if (MPD_ERROR(c))
214 return -1;
216 /* free the old status */
217 if (c->status)
218 mpd_freeStatus(c->status);
220 /* retreive new status */
221 mpd_sendStatusCommand(c->connection);
222 c->status = mpd_getStatus(c->connection);
223 if ((retval=mpdclient_finish_command(c)))
224 return retval;
226 /* check if the playlist needs an update */
227 if (c->playlist.id != c->status->playlist) {
228 if (playlist_is_empty(&c->playlist))
229 retval = mpdclient_playlist_update_changes(c);
230 else
231 retval = mpdclient_playlist_update(c);
232 }
234 /* update the current song */
235 if (!c->song || c->status->songid != c->song->id) {
236 c->song = playlist_get_song(c, c->status->song);
237 }
239 c->need_update = FALSE;
241 return retval;
242 }
245 /****************************************************************************/
246 /*** MPD Commands **********************************************************/
247 /****************************************************************************/
249 gint
250 mpdclient_cmd_play(mpdclient_t *c, gint idx)
251 {
252 #ifdef ENABLE_SONG_ID
253 struct mpd_song *song = playlist_get_song(c, idx);
255 if (song)
256 mpd_sendPlayIdCommand(c->connection, song->id);
257 else
258 mpd_sendPlayIdCommand(c->connection, MPD_PLAY_AT_BEGINNING);
259 #else
260 mpd_sendPlayCommand(c->connection, idx);
261 #endif
262 c->need_update = TRUE;
263 return mpdclient_finish_command(c);
264 }
266 gint
267 mpdclient_cmd_pause(mpdclient_t *c, gint value)
268 {
269 mpd_sendPauseCommand(c->connection, value);
270 return mpdclient_finish_command(c);
271 }
273 gint
274 mpdclient_cmd_crop(mpdclient_t *c)
275 {
276 mpd_Status *status;
277 int length;
279 mpd_sendStatusCommand(c->connection);
280 status = mpd_getStatus(c->connection);
281 length = status->playlistLength - 1;
283 if (length <= 0) {
284 mpd_freeStatus(status);
285 } else if (status->state == 3 || status->state == 2) {
286 /* If playing or paused */
288 mpd_sendCommandListBegin( c->connection );
290 while (length >= 0) {
291 if (length != status->song)
292 mpd_sendDeleteCommand(c->connection, length);
294 length--;
295 }
297 mpd_sendCommandListEnd(c->connection);
298 mpd_freeStatus(status);
299 } else {
300 mpd_freeStatus(status);
301 }
303 return mpdclient_finish_command(c);
304 }
306 gint
307 mpdclient_cmd_stop(mpdclient_t *c)
308 {
309 mpd_sendStopCommand(c->connection);
310 return mpdclient_finish_command(c);
311 }
313 gint
314 mpdclient_cmd_next(mpdclient_t *c)
315 {
316 mpd_sendNextCommand(c->connection);
317 c->need_update = TRUE;
318 return mpdclient_finish_command(c);
319 }
321 gint
322 mpdclient_cmd_prev(mpdclient_t *c)
323 {
324 mpd_sendPrevCommand(c->connection);
325 c->need_update = TRUE;
326 return mpdclient_finish_command(c);
327 }
329 gint
330 mpdclient_cmd_seek(mpdclient_t *c, gint id, gint pos)
331 {
332 mpd_sendSeekIdCommand(c->connection, id, pos);
333 return mpdclient_finish_command(c);
334 }
336 gint
337 mpdclient_cmd_shuffle(mpdclient_t *c)
338 {
339 mpd_sendShuffleCommand(c->connection);
340 c->need_update = TRUE;
341 return mpdclient_finish_command(c);
342 }
344 gint
345 mpdclient_cmd_clear(mpdclient_t *c)
346 {
347 gint retval = 0;
349 mpd_sendClearCommand(c->connection);
350 retval = mpdclient_finish_command(c);
351 /* call playlist updated callback */
352 mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL);
353 c->need_update = TRUE;
354 return retval;
355 }
357 gint
358 mpdclient_cmd_repeat(mpdclient_t *c, gint value)
359 {
360 mpd_sendRepeatCommand(c->connection, value);
361 return mpdclient_finish_command(c);
362 }
364 gint
365 mpdclient_cmd_random(mpdclient_t *c, gint value)
366 {
367 mpd_sendRandomCommand(c->connection, value);
368 return mpdclient_finish_command(c);
369 }
371 gint
372 mpdclient_cmd_crossfade(mpdclient_t *c, gint value)
373 {
374 mpd_sendCrossfadeCommand(c->connection, value);
375 return mpdclient_finish_command(c);
376 }
378 gint
379 mpdclient_cmd_db_update(mpdclient_t *c, gchar *path)
380 {
381 mpd_sendUpdateCommand(c->connection, path ? path : "");
382 return mpdclient_finish_command(c);
383 }
385 gint
386 mpdclient_cmd_volume(mpdclient_t *c, gint value)
387 {
388 mpd_sendSetvolCommand(c->connection, value);
389 return mpdclient_finish_command(c);
390 }
392 gint
393 mpdclient_cmd_add_path(mpdclient_t *c, gchar *path_utf8)
394 {
395 mpd_sendAddCommand(c->connection, path_utf8);
396 return mpdclient_finish_command(c);
397 }
399 gint
400 mpdclient_cmd_add(mpdclient_t *c, struct mpd_song *song)
401 {
402 gint retval = 0;
404 if( !song || !song->file )
405 return -1;
407 /* send the add command to mpd */
408 mpd_sendAddCommand(c->connection, song->file);
409 if( (retval=mpdclient_finish_command(c)) )
410 return retval;
412 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
413 /* add the song to playlist */
414 playlist_append(&c->playlist, song);
416 /* increment the playlist id, so we dont retrives a new playlist */
417 c->playlist.id++;
419 /* call playlist updated callback */
420 mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
421 #else
422 c->need_update = TRUE;
423 #endif
425 return 0;
426 }
428 gint
429 mpdclient_cmd_delete(mpdclient_t *c, gint idx)
430 {
431 gint retval = 0;
432 struct mpd_song *song;
434 if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
435 return -1;
437 song = playlist_get(&c->playlist, idx);
439 /* send the delete command to mpd */
440 #ifdef ENABLE_SONG_ID
441 mpd_sendDeleteIdCommand(c->connection, song->id);
442 #else
443 mpd_sendDeleteCommand(c->connection, idx);
444 #endif
445 if( (retval=mpdclient_finish_command(c)) )
446 return retval;
448 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
449 /* increment the playlist id, so we dont retrive a new playlist */
450 c->playlist.id++;
452 /* remove the song from the playlist */
453 playlist_remove_reuse(&c->playlist, idx);
455 /* call playlist updated callback */
456 mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song);
458 /* remove references to the song */
459 if (c->song == song) {
460 c->song = NULL;
461 c->need_update = TRUE;
462 }
464 mpd_freeSong(song);
466 #else
467 c->need_update = TRUE;
468 #endif
470 return 0;
471 }
473 gint
474 mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index)
475 {
476 gint n;
477 struct mpd_song *song1, *song2;
479 if (old_index == new_index || new_index < 0 ||
480 (guint)new_index >= c->playlist.list->len)
481 return -1;
483 song1 = playlist_get(&c->playlist, old_index);
484 song2 = playlist_get(&c->playlist, new_index);
486 /* send the move command to mpd */
487 #ifdef ENABLE_SONG_ID
488 mpd_sendSwapIdCommand(c->connection, song1->id, song2->id);
489 #else
490 mpd_sendMoveCommand(c->connection, old_index, new_index);
491 #endif
492 if( (n=mpdclient_finish_command(c)) )
493 return n;
495 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
496 /* update the playlist */
497 playlist_swap(&c->playlist, old_index, new_index);
499 /* increment the playlist id, so we dont retrives a new playlist */
500 c->playlist.id++;
502 #else
503 c->need_update = TRUE;
504 #endif
506 /* call playlist updated callback */
507 mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index);
509 return 0;
510 }
512 gint
513 mpdclient_cmd_save_playlist(mpdclient_t *c, gchar *filename_utf8)
514 {
515 gint retval = 0;
517 mpd_sendSaveCommand(c->connection, filename_utf8);
518 if ((retval = mpdclient_finish_command(c)) == 0)
519 mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL);
520 return retval;
521 }
523 gint
524 mpdclient_cmd_load_playlist(mpdclient_t *c, gchar *filename_utf8)
525 {
526 mpd_sendLoadCommand(c->connection, filename_utf8);
527 c->need_update = TRUE;
528 return mpdclient_finish_command(c);
529 }
531 gint
532 mpdclient_cmd_delete_playlist(mpdclient_t *c, gchar *filename_utf8)
533 {
534 gint retval = 0;
536 mpd_sendRmCommand(c->connection, filename_utf8);
537 if ((retval = mpdclient_finish_command(c)) == 0)
538 mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL);
539 return retval;
540 }
543 /****************************************************************************/
544 /*** Callback managment functions *******************************************/
545 /****************************************************************************/
547 static void
548 do_list_callbacks(mpdclient_t *c, GList *list, gint event, gpointer data)
549 {
550 while (list) {
551 mpdc_list_cb_t fn = list->data;
553 fn(c, event, data);
554 list = list->next;
555 }
556 }
558 void
559 mpdclient_playlist_callback(mpdclient_t *c, int event, gpointer data)
560 {
561 do_list_callbacks(c, c->playlist_callbacks, event, data);
562 }
564 void
565 mpdclient_install_playlist_callback(mpdclient_t *c,mpdc_list_cb_t cb)
566 {
567 c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb);
568 }
570 void
571 mpdclient_remove_playlist_callback(mpdclient_t *c, mpdc_list_cb_t cb)
572 {
573 c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb);
574 }
576 void
577 mpdclient_browse_callback(mpdclient_t *c, int event, gpointer data)
578 {
579 do_list_callbacks(c, c->browse_callbacks, event, data);
580 }
583 void
584 mpdclient_install_browse_callback(mpdclient_t *c,mpdc_list_cb_t cb)
585 {
586 c->browse_callbacks = g_list_append(c->browse_callbacks, cb);
587 }
589 void
590 mpdclient_remove_browse_callback(mpdclient_t *c, mpdc_list_cb_t cb)
591 {
592 c->browse_callbacks = g_list_remove(c->browse_callbacks, cb);
593 }
595 void
596 mpdclient_install_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
597 {
598 c->error_callbacks = g_list_append(c->error_callbacks, cb);
599 }
601 void
602 mpdclient_remove_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
603 {
604 c->error_callbacks = g_list_remove(c->error_callbacks, cb);
605 }
608 /****************************************************************************/
609 /*** Playlist managment functions *******************************************/
610 /****************************************************************************/
612 /* update playlist */
613 gint
614 mpdclient_playlist_update(mpdclient_t *c)
615 {
616 mpd_InfoEntity *entity;
618 if (MPD_ERROR(c))
619 return -1;
621 playlist_clear(&c->playlist);
623 mpd_sendPlaylistInfoCommand(c->connection,-1);
624 while ((entity = mpd_getNextInfoEntity(c->connection))) {
625 if (entity->type == MPD_INFO_ENTITY_TYPE_SONG)
626 playlist_append(&c->playlist, entity->info.song);
628 mpd_freeInfoEntity(entity);
629 }
631 c->playlist.id = c->status->playlist;
632 c->song = NULL;
634 /* call playlist updated callbacks */
635 mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
637 return mpdclient_finish_command(c);
638 }
640 #ifdef ENABLE_PLCHANGES
642 /* update playlist (plchanges) */
643 gint
644 mpdclient_playlist_update_changes(mpdclient_t *c)
645 {
646 mpd_InfoEntity *entity;
648 if (MPD_ERROR(c))
649 return -1;
651 mpd_sendPlChangesCommand(c->connection, c->playlist.id);
653 while ((entity = mpd_getNextInfoEntity(c->connection)) != NULL) {
654 struct mpd_song *song = entity->info.song;
656 if (song->pos >= 0 && (guint)song->pos < c->playlist.list->len) {
657 /* update song */
658 playlist_replace(&c->playlist, song->pos, song);
659 } else {
660 /* add a new song */
661 playlist_append(&c->playlist, song);
662 }
664 mpd_freeInfoEntity(entity);
665 }
667 /* remove trailing songs */
668 while ((guint)c->status->playlistLength < c->playlist.list->len) {
669 guint pos = c->playlist.list->len - 1;
671 /* Remove the last playlist entry */
672 playlist_remove(&c->playlist, pos);
673 }
675 c->song = NULL;
676 c->playlist.id = c->status->playlist;
678 mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
680 return 0;
681 }
683 #else
684 gint
685 mpdclient_playlist_update_changes(mpdclient_t *c)
686 {
687 return mpdclient_playlist_update(c);
688 }
689 #endif
692 /****************************************************************************/
693 /*** Filelist functions *****************************************************/
694 /****************************************************************************/
696 mpdclient_filelist_t *
697 mpdclient_filelist_get(mpdclient_t *c, const gchar *path)
698 {
699 mpdclient_filelist_t *filelist;
700 mpd_InfoEntity *entity;
701 gboolean has_dirs_only = TRUE;
703 mpd_sendLsInfoCommand(c->connection, path);
704 filelist = filelist_new(path);
705 if (path && path[0] && strcmp(path, "/"))
706 /* add a dummy entry for ./.. */
707 filelist_append(filelist, NULL);
709 while ((entity=mpd_getNextInfoEntity(c->connection))) {
710 filelist_append(filelist, entity);
712 if (has_dirs_only && entity->type != MPD_INFO_ENTITY_TYPE_DIRECTORY) {
713 has_dirs_only = FALSE;
714 }
715 }
717 /* If there's an error, ignore it. We'll return an empty filelist. */
718 mpdclient_finish_command(c);
720 // If there are only directory entities in the filelist, we sort it
721 if (has_dirs_only)
722 filelist_sort(filelist, compare_filelistentry_dir);
724 return filelist;
725 }
727 mpdclient_filelist_t *
728 mpdclient_filelist_search(mpdclient_t *c,
729 int exact_match,
730 int table,
731 gchar *filter_utf8)
732 {
733 mpdclient_filelist_t *filelist;
734 mpd_InfoEntity *entity;
736 if (exact_match)
737 mpd_sendFindCommand(c->connection, table, filter_utf8);
738 else
739 mpd_sendSearchCommand(c->connection, table, filter_utf8);
740 filelist = filelist_new(NULL);
742 while ((entity=mpd_getNextInfoEntity(c->connection)))
743 filelist_append(filelist, entity);
745 if (mpdclient_finish_command(c)) {
746 filelist_free(filelist);
747 return NULL;
748 }
750 return filelist;
751 }
753 mpdclient_filelist_t *
754 mpdclient_filelist_update(mpdclient_t *c, mpdclient_filelist_t *filelist)
755 {
756 if (filelist != NULL) {
757 gchar *path = g_strdup(filelist->path);
759 filelist_free(filelist);
760 filelist = mpdclient_filelist_get(c, path);
761 g_free(path);
762 return filelist;
763 }
764 return NULL;
765 }
767 int
768 mpdclient_filelist_add_all(mpdclient_t *c, mpdclient_filelist_t *fl)
769 {
770 guint i;
772 if (filelist_is_empty(fl))
773 return 0;
775 mpd_sendCommandListBegin(c->connection);
777 for (i = 0; i < filelist_length(fl); ++i) {
778 filelist_entry_t *entry = filelist_get(fl, i);
779 mpd_InfoEntity *entity = entry->entity;
781 if (entity && entity->type == MPD_INFO_ENTITY_TYPE_SONG) {
782 struct mpd_song *song = entity->info.song;
784 mpd_sendAddCommand(c->connection, song->file);
785 }
786 }
788 mpd_sendCommandListEnd(c->connection);
789 return mpdclient_finish_command(c);
790 }
792 GList *
793 mpdclient_get_artists(mpdclient_t *c)
794 {
795 gchar *str = NULL;
796 GList *list = NULL;
798 mpd_sendListCommand(c->connection, MPD_TABLE_ARTIST, NULL);
799 while ((str = mpd_getNextArtist(c->connection)))
800 list = g_list_append(list, (gpointer) str);
802 if (mpdclient_finish_command(c))
803 return string_list_free(list);
805 return list;
806 }
808 GList *
809 mpdclient_get_albums(mpdclient_t *c, gchar *artist_utf8)
810 {
811 gchar *str = NULL;
812 GList *list = NULL;
814 mpd_sendListCommand(c->connection, MPD_TABLE_ALBUM, artist_utf8);
815 while ((str = mpd_getNextAlbum(c->connection)))
816 list = g_list_append(list, (gpointer) str);
818 if (mpdclient_finish_command(c))
819 return string_list_free(list);
821 return list;
822 }