Code

Sort playlist if needed after plchanges,
[ncmpc.git] / src / mpdclient.c
1 /* 
2  * $Id$
3  *
4  * (c) 2004 by Kalle Wallin <kaw@linux.se>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  */
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24 #include <time.h>
25 #include <string.h>
26 #include <glib.h>
28 #include "config.h"
29 #include "ncmpc.h"
30 #include "support.h"
31 #include "mpdclient.h"
32 #include "options.h"
34 #undef  ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD /* broken with song id's */
35 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
36 #define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
37 #define ENABLE_SONG_ID
39 #define MPD_ERROR(c) (c==NULL || c->connection==NULL || c->connection->error)
42 /* Error callbacks */
43 static gint
44 error_cb(mpdclient_t *c, gint error, gchar *msg)
45 {
46   GList *list = c->error_callbacks;
47   
48   if( list==NULL )
49     fprintf(stderr, "error [%d]: %s\n", (error & 0xFF), msg);
51   while(list)
52     {
53       mpdc_error_cb_t cb = list->data;
54       if( cb )
55         cb(c, error, msg);
56       list=list->next;
57     }
58   mpd_clearError(c->connection);
59   return error;
60 }
62 #ifdef DEBUG
63 #include "strfsong.h"
65 static gchar *
66 get_song_name(mpd_Song *song)
67 {
68   static gchar name[256];
70   strfsong(name, 256, "[%artist% - ]%title%|%file%", song);
71   return name;
72 }
74 #endif
76 /****************************************************************************/
77 /*** mpdclient functions ****************************************************/
78 /****************************************************************************/
80 gint
81 mpdclient_finish_command(mpdclient_t *c) 
82 {
83   mpd_finishCommand(c->connection);
85   if( c->connection->error )
86     {
87       gchar *msg = locale_to_utf8(c->connection->errorStr);
88       gint error = c->connection->error;
89       
90       if( error == MPD_ERROR_ACK )
91         error = error | (c->connection->errorCode << 8);
93       error_cb(c, error, msg);
94       g_free(msg);
95       return error;
96     }
98   return 0;
99 }
101 mpdclient_t *
102 mpdclient_new(void)
104   mpdclient_t *c;
106   c = g_malloc0(sizeof(mpdclient_t));
108   return c;
111 mpdclient_t *
112 mpdclient_free(mpdclient_t *c)
114   mpdclient_disconnect(c);
115   g_list_free(c->error_callbacks);
116   g_list_free(c->playlist_callbacks);
117   g_list_free(c->browse_callbacks);
118   g_free(c);
120   return NULL;
123 gint
124 mpdclient_disconnect(mpdclient_t *c)
126   if( c->connection )
127     mpd_closeConnection(c->connection);
128   c->connection = NULL;
130   if( c->status )
131     mpd_freeStatus(c->status);
132   c->status = NULL;
134   if( c->playlist.list )
135     mpdclient_playlist_free(&c->playlist);
137   if( c->song )
138     c->song = NULL;
139   
140   return 0;
143 gint
144 mpdclient_connect(mpdclient_t *c, 
145                   gchar *host, 
146                   gint port, 
147                   gfloat timeout,
148                   gchar *password)
150   gint retval = 0;
151   
152   /* close any open connection */
153   if( c->connection )
154     mpdclient_disconnect(c);
156   /* connect to MPD */
157   c->connection = mpd_newConnection(host, port, timeout);
158   if( c->connection->error )
159     return error_cb(c, c->connection->error, c->connection->errorStr);
161   /* send password */
162   if( password )
163     {
164       mpd_sendPasswordCommand(c->connection, password);
165       retval = mpdclient_finish_command(c);
166     }
167   c->need_update = TRUE;
169   return retval;
172 gint
173 mpdclient_update(mpdclient_t *c)
175   gint retval = 0;
177   if( MPD_ERROR(c) )
178     return -1;
180   /* free the old status */
181   if( c->status )
182     mpd_freeStatus(c->status);
183   
184   /* retreive new status */
185   mpd_sendStatusCommand(c->connection);
186   c->status = mpd_getStatus(c->connection);
187   if( (retval=mpdclient_finish_command(c)) )
188     return retval;
189 #ifdef DEBUG
190   if( c->status->error )
191     D("status> %s\n", c->status->error);
192 #endif
194   /* check if the playlist needs an update */
195   if( c->playlist.id != c->status->playlist )
196     {
197       if( c->playlist.list )
198         retval = mpdclient_playlist_update_changes(c);
199       else
200         retval = mpdclient_playlist_update(c);
201     }
203   /* update the current song */
204   if( !c->song || c->status->songid != c->song->id )
205     {
206       c->song = playlist_get_song(c, c->status->song);
207     }
209   c->need_update = FALSE;
211   return retval;
215 /****************************************************************************/
216 /*** MPD Commands  **********************************************************/
217 /****************************************************************************/
219 gint 
220 mpdclient_cmd_play(mpdclient_t *c, gint index)
222 #ifdef ENABLE_SONG_ID
223   mpd_Song *song = playlist_get_song(c, index);
225   D("Play id:%d\n", song ? song->id : -1);
226   if( song )
227     mpd_sendPlayIdCommand(c->connection, song->id);
228   else
229     mpd_sendPlayIdCommand(c->connection, MPD_PLAY_AT_BEGINNING);
230 #else
231   mpd_sendPlayCommand(c->connection, index);
232 #endif
233   c->need_update = TRUE;
234   return mpdclient_finish_command(c);
237 gint 
238 mpdclient_cmd_pause(mpdclient_t *c, gint value)
240   mpd_sendPauseCommand(c->connection, value);
241   return mpdclient_finish_command(c);
244 gint 
245 mpdclient_cmd_stop(mpdclient_t *c)
247   mpd_sendStopCommand(c->connection);
248   return mpdclient_finish_command(c);
251 gint 
252 mpdclient_cmd_next(mpdclient_t *c)
254   mpd_sendNextCommand(c->connection);
255   c->need_update = TRUE;
256   return mpdclient_finish_command(c);
259 gint 
260 mpdclient_cmd_prev(mpdclient_t *c)
262   mpd_sendPrevCommand(c->connection);
263   c->need_update = TRUE;
264   return mpdclient_finish_command(c);
267 gint 
268 mpdclient_cmd_seek(mpdclient_t *c, gint id, gint pos)
270   D("Seek id:%d\n", id);
271   mpd_sendSeekIdCommand(c->connection, id, pos);
272   return mpdclient_finish_command(c);
275 gint 
276 mpdclient_cmd_shuffle(mpdclient_t *c)
278   mpd_sendShuffleCommand(c->connection);
279   c->need_update = TRUE;
280   return mpdclient_finish_command(c);
283 gint 
284 mpdclient_cmd_clear(mpdclient_t *c)
286   gint retval = 0;
288   mpd_sendClearCommand(c->connection);
289   retval = mpdclient_finish_command(c);
290   /* call playlist updated callback */
291   mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL);
292   c->need_update = TRUE;
293   return retval;
296 gint 
297 mpdclient_cmd_repeat(mpdclient_t *c, gint value)
299   mpd_sendRepeatCommand(c->connection, value);
300   return mpdclient_finish_command(c);
303 gint 
304 mpdclient_cmd_random(mpdclient_t *c, gint value)
306   mpd_sendRandomCommand(c->connection, value);
307   return mpdclient_finish_command(c);
310 gint 
311 mpdclient_cmd_crossfade(mpdclient_t *c, gint value)
313   mpd_sendCrossfadeCommand(c->connection, value);
314   return mpdclient_finish_command(c);
317 gint 
318 mpdclient_cmd_db_update(mpdclient_t *c)
320   mpd_sendUpdateCommand(c->connection);
321   return mpdclient_finish_command(c);
324 gint 
325 mpdclient_cmd_volume(mpdclient_t *c, gint value)
327   mpd_sendSetvolCommand(c->connection, value);
328   return mpdclient_finish_command(c);
331 gint 
332 mpdclient_cmd_add(mpdclient_t *c, mpd_Song *song)
333
334   gint retval = 0;
336   if( !song || !song->file )
337     return -1;
339   /* send the add command to mpd */
340   mpd_sendAddCommand(c->connection, song->file);
341   if( (retval=mpdclient_finish_command(c)) )
342     return retval;
344 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
345   /* add the song to playlist */
346   c->playlist.list = g_list_append(c->playlist.list, mpd_songDup(song));
347   c->playlist.length++;
349   /* increment the playlist id, so we dont retrives a new playlist */
350   c->playlist.id++;
352   /* call playlist updated callback */
353   mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
354 #else
355   c->need_update = TRUE;
356 #endif
358   return 0;
361 gint
362 mpdclient_cmd_delete(mpdclient_t *c, gint index)
364   gint retval = 0;
365   mpd_Song *song = playlist_get_song(c, index);
367   if( !song )
368     return -1;
370   /* send the delete command to mpd */
371 #ifdef ENABLE_SONG_ID
372   D("Delete id:%d\n", song->id);
373   mpd_sendDeleteIdCommand(c->connection, song->id);
374 #else
375   mpd_sendDeleteCommand(c->connection, index);
376 #endif
377   if( (retval=mpdclient_finish_command(c)) )
378     return retval;
380 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
381   /* increment the playlist id, so we dont retrive a new playlist */
382   c->playlist.id++;
384   /* remove the song from the playlist */
385   c->playlist.list = g_list_remove(c->playlist.list, (gpointer) song);
386   c->playlist.length = g_list_length(c->playlist.list);
388   /* call playlist updated callback */
389   mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song);
391   /* remove references to the song */
392   if( c->song == song )
393     {
394       c->song = NULL;   
395       c->need_update = TRUE;
396     }
398   /* free song */
399   mpd_freeSong(song);  
401 #else
402   c->need_update = TRUE;
403 #endif
405   return 0;
408 gint
409 mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index)
411   gint n, index1, index2;
412   GList *item1, *item2;
413   gpointer data1, data2;
414   mpd_Song *song1, *song2;
416   if( old_index==new_index || new_index<0 || new_index>=c->playlist.length )
417     return -1;
419   song1 = playlist_get_song(c, old_index);
420   song2 = playlist_get_song(c, new_index);
422   /* send the move command to mpd */  
423 #ifdef ENABLE_SONG_ID
424   D("Swaping id:%d with id:%d\n", song1->id, song2->id);
425   mpd_sendSwapIdCommand(c->connection, song1->id, song2->id);
426 #else
427   D("Moving index %d to id:%d\n", old_index, new_index);
428   mpd_sendMoveCommand(c->connection, old_index, new_index);
429 #endif
430   if( (n=mpdclient_finish_command(c)) )
431     return n;
433 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
434   /* update the songs position field */
435   n = song1->pos;
436   song1->pos = song2->pos;
437   song2->pos = n;
438   index1 = MIN(old_index, new_index);
439   index2 = MAX(old_index, new_index);
440   /* retreive the list items */
441   item1 = g_list_nth(c->playlist.list, index1);
442   item2 = g_list_nth(c->playlist.list, index2);
443   /* retrieve the songs */
444   data1 = item1->data;
445   data2 = item2->data;
447   /* move the second item */
448   c->playlist.list = g_list_remove(c->playlist.list, data2);
449   c->playlist.list = g_list_insert_before(c->playlist.list, item1, data2);
451   /* move the first item */
452   if( index2-index1 >1 )
453     {
454       item2 = g_list_nth(c->playlist.list, index2);
455       c->playlist.list = g_list_remove(c->playlist.list, data1);
456       c->playlist.list = g_list_insert_before(c->playlist.list, item2, data1);
457     }
459   /* increment the playlist id, so we dont retrives a new playlist */
460   c->playlist.id++;
462 #else
463   c->need_update = TRUE;
464 #endif 
466   /* call playlist updated callback */
467   mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index);
469   return 0;
472 gint 
473 mpdclient_cmd_save_playlist(mpdclient_t *c, gchar *filename)
475   gint retval = 0;
476   gchar *filename_utf8 = locale_to_utf8(filename);
478   mpd_sendSaveCommand(c->connection, filename_utf8);
479   if( (retval=mpdclient_finish_command(c)) == 0 )
480     mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL);
481   g_free(filename_utf8);
482   return retval;
485 gint 
486 mpdclient_cmd_load_playlist(mpdclient_t *c, gchar *filename_utf8)
488   mpd_sendLoadCommand(c->connection, filename_utf8);
489   c->need_update = TRUE;
490   return mpdclient_finish_command(c);
493 gint 
494 mpdclient_cmd_delete_playlist(mpdclient_t *c, gchar *filename_utf8)
496   gint retval = 0;
498   mpd_sendRmCommand(c->connection, filename_utf8);
499   if( (retval=mpdclient_finish_command(c)) == 0 )
500     mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL);
501   return retval;
505 /****************************************************************************/
506 /*** Callback managment functions *******************************************/
507 /****************************************************************************/
508 static void
509 do_list_callbacks(mpdclient_t *c, GList *list, gint event, gpointer data)
511   while(list)
512     {
513       mpdc_list_cb_t fn = list->data;
515       fn(c, event, data);
516       list=list->next;
517     }
520 void
521 mpdclient_playlist_callback(mpdclient_t *c, int event, gpointer data)
523   do_list_callbacks(c, c->playlist_callbacks, event, data);
526 void
527 mpdclient_install_playlist_callback(mpdclient_t *c,mpdc_list_cb_t cb)
529   c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb);
532 void
533 mpdclient_remove_playlist_callback(mpdclient_t *c, mpdc_list_cb_t cb)
535   c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb);
538 void
539 mpdclient_browse_callback(mpdclient_t *c, int event, gpointer data)
541   do_list_callbacks(c, c->browse_callbacks, event, data);
545 void
546 mpdclient_install_browse_callback(mpdclient_t *c,mpdc_list_cb_t cb)
548   c->browse_callbacks = g_list_append(c->browse_callbacks, cb);
551 void
552 mpdclient_remove_browse_callback(mpdclient_t *c, mpdc_list_cb_t cb)
554   c->browse_callbacks = g_list_remove(c->browse_callbacks, cb);
557 void
558 mpdclient_install_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
560   c->error_callbacks = g_list_append(c->error_callbacks, cb);
563 void
564 mpdclient_remove_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
566   c->error_callbacks = g_list_remove(c->error_callbacks, cb);
569 /****************************************************************************/
570 /*** Playlist managment functions *******************************************/
571 /****************************************************************************/
573 gint
574 mpdclient_playlist_free(mpdclient_playlist_t *playlist)
576   GList *list = g_list_first(playlist->list);
578   while(list)
579     {
580       mpd_Song *song = (mpd_Song *) list->data;
581       mpd_freeSong(song);
582       list=list->next;
583     }
584   g_list_free(playlist->list);
585   memset(playlist, 0, sizeof(mpdclient_playlist_t));
586   return 0;
589 /* update playlist */
590 gint 
591 mpdclient_playlist_update(mpdclient_t *c)
593   mpd_InfoEntity *entity;
595   D("mpdclient_playlist_update() [%lld]\n", c->status->playlist);
597   if( MPD_ERROR(c) )
598     return -1;
600   if( c->playlist.list )
601     mpdclient_playlist_free(&c->playlist);
603   mpd_sendPlaylistInfoCommand(c->connection,-1);
604   while( (entity=mpd_getNextInfoEntity(c->connection)) ) 
605     {
606       if(entity->type==MPD_INFO_ENTITY_TYPE_SONG) 
607         {
608           mpd_Song *song = mpd_songDup(entity->info.song);
610           c->playlist.list = g_list_append(c->playlist.list, (gpointer) song);
611           c->playlist.length++;
612         }
613       mpd_freeInfoEntity(entity);
614     }
615   c->playlist.id = c->status->playlist;
616   c->song = NULL;
617   c->playlist.updated = TRUE;
619   /* call playlist updated callbacks */
620   mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
622   return mpdclient_finish_command(c);
625 static gint 
626 compare_songs(gconstpointer a, gconstpointer b)
628   mpd_Song *song1 = (mpd_Song *) a; 
629   mpd_Song *song2 = (mpd_Song *) b; 
631   return song1->pos - song2->pos;
634 /* update playlist (plchanges) */
635 gint 
636 mpdclient_playlist_update_changes(mpdclient_t *c)
638   gboolean sort = FALSE;
639   mpd_InfoEntity *entity;
641   D("mpdclient_playlist_update_changes() [%lld -> %lld]\n", 
642     c->status->playlist, c->playlist.id);
644   if( MPD_ERROR(c) )
645     return -1;
647   mpd_sendPlChangesCommand(c->connection, c->playlist.id); 
649   while( (entity=mpd_getNextInfoEntity(c->connection)) != NULL   ) 
650     {
651       if(entity->type==MPD_INFO_ENTITY_TYPE_SONG) 
652         {
653           mpd_Song *song;
654           GList *item;
656           if( (song=mpd_songDup(entity->info.song)) == NULL )
657             {
658               D("song==NULL => calling mpdclient_playlist_update()\n");
659               return mpdclient_playlist_update(c);
660             }
662           item =  playlist_lookup(c, song->id);
664           if( item && item->data)
665             {
666               /* Update playlist entry */
667               mpd_freeSong((mpd_Song *) item->data);
668               item->data = song;
669               if( c->song && c->song->id == song->id )
670                 c->song = song;
671               if( !sort && g_list_position(c->playlist.list, item)!=song->pos )
672                 sort = TRUE;
673               D("Changing index %d, num %d [%d] to %s\n",
674                 g_list_position(c->playlist.list, item),
675                 song->pos, song->id, get_song_name(song));
676             }
677           else
678             {
679               /* Add a new  playlist entry */
680               D("Adding pos:%d, id;%d - %s\n",
681                 song->pos, song->id, get_song_name(song));
682               c->playlist.list = g_list_append(c->playlist.list, 
683                                           (gpointer) song);
684               c->playlist.length++;
685             }
686         }
687       mpd_freeInfoEntity(entity);      
688     }
689   mpd_finishCommand(c->connection);
690   
691   while( g_list_length(c->playlist.list) > c->status->playlistLength )
692     {
693       GList *item = g_list_last(c->playlist.list);
695       /* Remove the last playlist entry */
696       mpd_freeSong((mpd_Song *) item->data);
697       c->playlist.list = g_list_delete_link(c->playlist.list, item);
698       c->playlist.length--;      
699       D("Removed the last playlist entry\n");
700     }
702   if( sort )
703     {
704       D("Sorting playlist...\n");
705       c->playlist.list = g_list_sort(c->playlist.list, compare_songs );
706     }
708   c->playlist.id = c->status->playlist;
709   c->playlist.updated = TRUE;
711   mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
713   return 0;
716 mpd_Song *
717 playlist_get_song(mpdclient_t *c, gint index)
719   return (mpd_Song *) g_list_nth_data(c->playlist.list, index);
722 GList *
723 playlist_lookup(mpdclient_t *c, gint id)
725   GList *list = c->playlist.list;
727   while( list )
728     {
729       mpd_Song *song = (mpd_Song *) list->data;
730       if( song->id == id )
731         return list;
732       list=list->next;
733     }
734   return NULL;
737 mpd_Song *
738 playlist_lookup_song(mpdclient_t *c, gint id)
740   GList *list = c->playlist.list;
742   while( list )
743     {
744       mpd_Song *song = (mpd_Song *) list->data;
745       if( song->id == id )
746         return song;
747       list=list->next;
748     }
749   return NULL;
752 gint 
753 playlist_get_index(mpdclient_t *c, mpd_Song *song)
755   return g_list_index(c->playlist.list, song);
758 gint 
759 playlist_get_index_from_id(mpdclient_t *c, gint id)
761   return g_list_index(c->playlist.list, playlist_lookup_song(c, id));
764 gint
765 playlist_get_index_from_file(mpdclient_t *c, gchar *filename)
767   GList *list = c->playlist.list;
768   gint i=0;
770   while( list )
771     {
772       mpd_Song *song = (mpd_Song *) list->data;
773       if( strcmp(song->file, filename ) == 0 )  
774         return i;
775       list=list->next;
776       i++;
777     }
778   return -1;
782 /****************************************************************************/
783 /*** Filelist functions *****************************************************/
784 /****************************************************************************/
786 mpdclient_filelist_t *
787 mpdclient_filelist_free(mpdclient_filelist_t *filelist)
789   GList *list = g_list_first(filelist->list);
791   D("mpdclient_filelist_free()\n");
792   while( list!=NULL )
793     {
794       filelist_entry_t *entry = list->data;
796       if( entry->entity )
797         mpd_freeInfoEntity(entry->entity);
798       g_free(entry);
799       list=list->next;
800     }
801   g_list_free(filelist->list);
802   g_free(filelist->path);
803   filelist->path = NULL;
804   filelist->list = NULL;
805   filelist->length = 0;
806   g_free(filelist);
808   return NULL;
812 mpdclient_filelist_t *
813 mpdclient_filelist_get(mpdclient_t *c, gchar *path)
815   mpdclient_filelist_t *filelist;
816   mpd_InfoEntity *entity;
817   gchar *path_utf8 = locale_to_utf8(path);
819   D("mpdclient_filelist_get(%s)\n", path);
820   mpd_sendLsInfoCommand(c->connection, path_utf8);
821   filelist = g_malloc0(sizeof(mpdclient_filelist_t));
822   if( path && path[0] && strcmp(path, "/") )
823     {
824       /* add a dummy entry for ./.. */
825       filelist_entry_t *entry = g_malloc0(sizeof(filelist_entry_t));
826       entry->entity = NULL;
827       filelist->list = g_list_append(filelist->list, (gpointer) entry);
828       filelist->length++;
829     }
831   while( (entity=mpd_getNextInfoEntity(c->connection)) ) 
832     {
833       filelist_entry_t *entry = g_malloc0(sizeof(filelist_entry_t));
834       
835       entry->entity = entity;
836       filelist->list = g_list_append(filelist->list, (gpointer) entry);
837       filelist->length++;
838     }
839   
840   if( mpdclient_finish_command(c) )
841     {
842       g_free(path_utf8);
843       return mpdclient_filelist_free(filelist);
844     }
845   
846   g_free(path_utf8);
847   filelist->path = g_strdup(path);
848   filelist->updated = TRUE;
850   return filelist;
853 mpdclient_filelist_t *
854 mpdclient_filelist_update(mpdclient_t *c, mpdclient_filelist_t *filelist)
856   if( filelist != NULL )
857     {    
858       gchar *path = g_strdup(filelist->path);
860       filelist = mpdclient_filelist_free(filelist);
861       filelist = mpdclient_filelist_get(c, path);
862       g_free(path);
863       return filelist;
864     }
865   return NULL;
868 filelist_entry_t *
869 mpdclient_filelist_find_song(mpdclient_filelist_t *fl, mpd_Song *song)
871   GList *list = g_list_first(fl->list);
873   while( list && song)
874     {
875       filelist_entry_t *entry = list->data;
876       mpd_InfoEntity *entity  = entry->entity;
878       if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG )
879         {
880           mpd_Song *song2 = entity->info.song;
882           if( strcmp(song->file, song2->file) == 0 )
883             {
884               return entry;
885             }
886         }
887       list = list->next;
888     }
889   return NULL;