Code

Major cleanup of the mpd client code (mpc->mpdclient)
[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 #undef  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, 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 retval = c->connection->error;
90       error_cb(c, c->connection->error, msg);
91       g_free(msg);
92       return retval;
93     }
95   return 0;
96 }
98 mpdclient_t *
99 mpdclient_new(void)
101   mpdclient_t *c;
103   c = g_malloc0(sizeof(mpdclient_t));
105   return c;
108 mpdclient_t *
109 mpdclient_free(mpdclient_t *c)
111   mpdclient_disconnect(c);
112   g_list_free(c->error_callbacks);
113   g_list_free(c->playlist_callbacks);
114   g_list_free(c->browse_callbacks);
115   g_free(c);
117   return NULL;
120 gint
121 mpdclient_disconnect(mpdclient_t *c)
123   D("mpdclient_disconnect()...\n");
124   if( c->connection )
125     mpd_closeConnection(c->connection);
126   c->connection = NULL;
128   if( c->status )
129     mpd_freeStatus(c->status);
130   c->status = NULL;
132   if( c->playlist.list )
133     mpdclient_playlist_free(&c->playlist);
135   if( c->song )
136     c->song = NULL;
137   
138   return 0;
141 gint
142 mpdclient_connect(mpdclient_t *c, 
143                   gchar *host, 
144                   gint port, 
145                   gfloat timeout,
146                   gchar *password)
148   gint retval = 0;
149   
150   /* close any open connection */
151   if( c->connection )
152     mpdclient_disconnect(c);
154   /* connect to MPD */
155   D("mpdclient_connect(%s, %d)...\n", host, port);
156   c->connection = mpd_newConnection(host, port, timeout);
157   if( c->connection->error )
158     return error_cb(c, c->connection->error, c->connection->errorStr);
160   /* send password */
161   if( password )
162     {
163       mpd_sendPasswordCommand(c->connection, password);
164       retval = mpdclient_finish_command(c);
165     }
167   return retval;
170 gint
171 mpdclient_update(mpdclient_t *c)
173   gint retval = 0;
175   if( MPD_ERROR(c) )
176     return -1;
178   /* free the old status */
179   if( c->status )
180     mpd_freeStatus(c->status);
181   
182   /* retreive new status */
183   mpd_sendStatusCommand(c->connection);
184   c->status = mpd_getStatus(c->connection);
185   if( (retval=mpdclient_finish_command(c)) )
186     return retval;
187 #ifdef DEBUG
188   if( c->status->error )
189     D("status> %s\n", c->status->error);
190 #endif
192   /* check if the playlist needs an update */
193   if( c->playlist.id != c->status->playlist )
194     {
195       if( c->playlist.list )
196         retval = mpdclient_playlist_update_changes(c);
197       else
198         retval = mpdclient_playlist_update(c);
199     }
201   /* update the current song */
202   if( !c->song || c->status->songid != c->song->id )
203     {
204       c->song = playlist_get_song(c, c->status->song);
205     }
207   c->need_update = FALSE;
209   return retval;
213 /****************************************************************************/
214 /*** MPD Commands  **********************************************************/
215 /****************************************************************************/
217 gint 
218 mpdclient_cmd_play(mpdclient_t *c, gint index)
220 #ifdef ENABLE_SONG_ID
221   mpd_Song *song = playlist_get_song(c, index);
223   if( song )
224     mpd_sendPlayIdCommand(c->connection, song->id);
225   else
226     mpd_sendPlayIdCommand(c->connection, MPD_PLAY_AT_BEGINNING);
227 #else
228   mpd_sendPlayCommand(c->connection, index);
229 #endif
230   c->need_update = TRUE;
231   return mpdclient_finish_command(c);
234 gint 
235 mpdclient_cmd_pause(mpdclient_t *c, gint value)
237   mpd_sendPauseCommand(c->connection, value);
238   return mpdclient_finish_command(c);
241 gint 
242 mpdclient_cmd_stop(mpdclient_t *c)
244   mpd_sendStopCommand(c->connection);
245   return mpdclient_finish_command(c);
248 gint 
249 mpdclient_cmd_next(mpdclient_t *c)
251   mpd_sendNextCommand(c->connection);
252   c->need_update = TRUE;
253   return mpdclient_finish_command(c);
256 gint 
257 mpdclient_cmd_prev(mpdclient_t *c)
259   mpd_sendPrevCommand(c->connection);
260   c->need_update = TRUE;
261   return mpdclient_finish_command(c);
264 gint 
265 mpdclient_cmd_seek(mpdclient_t *c, gint id, gint pos)
267   mpd_sendSeekIdCommand(c->connection, id, pos);
268   return mpdclient_finish_command(c);
271 gint 
272 mpdclient_cmd_shuffle(mpdclient_t *c)
274   mpd_sendShuffleCommand(c->connection);
275   c->need_update = TRUE;
276   return mpdclient_finish_command(c);
279 gint 
280 mpdclient_cmd_clear(mpdclient_t *c)
282   gint retval = 0;
284   mpd_sendClearCommand(c->connection);
285   retval = mpdclient_finish_command(c);
286   /* call playlist updated callback */
287   mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL);
288   c->need_update = TRUE;
289   return retval;
292 gint 
293 mpdclient_cmd_repeat(mpdclient_t *c, gint value)
295   mpd_sendRepeatCommand(c->connection, value);
296   return mpdclient_finish_command(c);
299 gint 
300 mpdclient_cmd_random(mpdclient_t *c, gint value)
302   mpd_sendRandomCommand(c->connection, value);
303   return mpdclient_finish_command(c);
306 gint 
307 mpdclient_cmd_crossfade(mpdclient_t *c, gint value)
309   mpd_sendCrossfadeCommand(c->connection, value);
310   return mpdclient_finish_command(c);
313 gint 
314 mpdclient_cmd_db_update(mpdclient_t *c)
316   mpd_sendUpdateCommand(c->connection);
317   return mpdclient_finish_command(c);
320 gint 
321 mpdclient_cmd_volume(mpdclient_t *c, gint value)
323   mpd_sendSetvolCommand(c->connection, value);
324   return mpdclient_finish_command(c);
327 gint 
328 mpdclient_cmd_add(mpdclient_t *c, mpd_Song *song)
329
330   gint retval = 0;
332   if( !song || !song->file )
333     return -1;
335   /* send the add command to mpd */
336   mpd_sendAddCommand(c->connection, song->file);
337   if( (retval=mpdclient_finish_command(c)) )
338     return retval;
340 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
341   /* add the song to playlist */
342   c->playlist.list = g_list_append(c->playlist.list, mpd_songDup(song));
343   c->playlist.length++;
345   /* increment the playlist id, so we dont retrives a new playlist */
346   c->playlist.id++;
348   /* call playlist updated callback */
349   mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
350 #else
351   c->need_update = TRUE;
352 #endif
354   return 0;
357 gint
358 mpdclient_cmd_delete(mpdclient_t *c, gint index)
360   gint retval = 0;
361   mpd_Song *song = playlist_get_song(c, index);
363   if( !song )
364     return -1;
366   /* send the delete command to mpd */
367 #ifdef ENABLE_SONG_ID
368   mpd_sendDeleteIdCommand(c->connection, song->id);
369 #else
370   mpd_sendDeleteCommand(c->connection, index);
371 #endif
372   if( (retval=mpdclient_finish_command(c)) )
373     return retval;
375 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
376   /* increment the playlist id, so we dont retrive a new playlist */
377   c->playlist.id++;
379   /* remove the song from the playlist */
380   c->playlist.list = g_list_remove(c->playlist.list, (gpointer) song);
381   c->playlist.length = g_list_length(c->playlist.list);
383   /* call playlist updated callback */
384   mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song);
386   /* remove references to the song */
387   if( c->song == song )
388     {
389       c->song = NULL;   
390       c->need_update = TRUE;
391     }
393   /* free song */
394   mpd_freeSong(song);  
396 #else
397   c->need_update = TRUE;
398 #endif
400   return 0;
403 gint
404 mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index)
406   gint retval, index1, index2;
407   GList *item1, *item2;
408   gpointer data1, data2;
409   mpd_Song *song1, *song2;
411   if( old_index==new_index || new_index<0 || new_index>=c->playlist.length )
412     return -1;
414   song1 = playlist_get_song(c, old_index);
415   song2 = playlist_get_song(c, new_index);
417   /* send the move command to mpd */  
418 #ifdef ENABLE_SONG_ID
419   mpd_sendMoveIdCommand(c->connection, song1->id, song2->id);
420 #else
421   mpd_sendMoveCommand(c->connection, old_index, new_index);
422 #endif
423   if( (retval=mpdclient_finish_command(c)) )
424     return retval;
426 #ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
427   index1 = MIN(old_index, new_index);
428   index2 = MAX(old_index, new_index);
429   item1 = g_list_nth(c->playlist.list, index1);
430   item2 = g_list_nth(c->playlist.list, index2);
431   data1 = item1->data;
432   data2 = item2->data;
434   /* move the second item */
435   c->playlist.list = g_list_remove(c->playlist.list, data2);
436   c->playlist.list = g_list_insert_before(c->playlist.list, item1, data2);
438   /* move the first item */
439   if( index2-index1 >1 )
440     {
441       item2 = g_list_nth(c->playlist.list, index2);
442       c->playlist.list = g_list_remove(c->playlist.list, data1);
443       c->playlist.list = g_list_insert_before(c->playlist.list, item2, data1);
444     }
446   /* increment the playlist id, so we dont retrives a new playlist */
447   c->playlist.id++;
449   /* call playlist updated callback */
450   mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index);
451 #else
452   c->need_update = TRUE;
453 #endif 
455   return 0;
458 gint 
459 mpdclient_cmd_save_playlist(mpdclient_t *c, gchar *filename)
461   gint retval = 0;
462   gchar *filename_utf8 = locale_to_utf8(filename);
464   mpd_sendSaveCommand(c->connection, filename_utf8);
465   if( (retval=mpdclient_finish_command(c)) == 0 )
466     mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL);
467   g_free(filename_utf8);
468   return retval;
471 gint 
472 mpdclient_cmd_load_playlist(mpdclient_t *c, gchar *filename_utf8)
474   mpd_sendLoadCommand(c->connection, filename_utf8);
475   c->need_update = TRUE;
476   return mpdclient_finish_command(c);
479 gint 
480 mpdclient_cmd_delete_playlist(mpdclient_t *c, gchar *filename_utf8)
482   gint retval = 0;
484   mpd_sendRmCommand(c->connection, filename_utf8);
485   if( (retval=mpdclient_finish_command(c)) == 0 )
486     mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL);
487   return retval;
491 /****************************************************************************/
492 /*** Callback managment functions *******************************************/
493 /****************************************************************************/
494 static void
495 do_list_callbacks(mpdclient_t *c, GList *list, gint event, gpointer data)
497   while(list)
498     {
499       mpdc_list_cb_t fn = list->data;
501       fn(c, event, data);
502       list=list->next;
503     }
506 void
507 mpdclient_playlist_callback(mpdclient_t *c, int event, gpointer data)
509   do_list_callbacks(c, c->playlist_callbacks, event, data);
512 void
513 mpdclient_install_playlist_callback(mpdclient_t *c,mpdc_list_cb_t cb)
515   c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb);
518 void
519 mpdclient_remove_playlist_callback(mpdclient_t *c, mpdc_list_cb_t cb)
521   c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb);
524 void
525 mpdclient_browse_callback(mpdclient_t *c, int event, gpointer data)
527   do_list_callbacks(c, c->browse_callbacks, event, data);
531 void
532 mpdclient_install_browse_callback(mpdclient_t *c,mpdc_list_cb_t cb)
534   c->browse_callbacks = g_list_append(c->browse_callbacks, cb);
537 void
538 mpdclient_remove_browse_callback(mpdclient_t *c, mpdc_list_cb_t cb)
540   c->browse_callbacks = g_list_remove(c->browse_callbacks, cb);
543 void
544 mpdclient_install_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
546   c->error_callbacks = g_list_append(c->error_callbacks, cb);
549 void
550 mpdclient_remove_error_callback(mpdclient_t *c, mpdc_error_cb_t cb)
552   c->error_callbacks = g_list_remove(c->error_callbacks, cb);
555 /****************************************************************************/
556 /*** Playlist managment functions *******************************************/
557 /****************************************************************************/
559 gint
560 mpdclient_playlist_free(mpdclient_playlist_t *playlist)
562   GList *list = g_list_first(playlist->list);
564   while(list)
565     {
566       mpd_Song *song = (mpd_Song *) list->data;
567       mpd_freeSong(song);
568       list=list->next;
569     }
570   g_list_free(playlist->list);
571   playlist->list   = NULL;
572   playlist->length = 0;
573   return 0;
576 /* update playlist */
577 gint 
578 mpdclient_playlist_update(mpdclient_t *c)
580   mpd_InfoEntity *entity;
582   D("mpdclient_playlist_update() [%lld]\n", c->status->playlist);
584   if( MPD_ERROR(c) )
585     return -1;
587   if( c->playlist.list )
588     mpdclient_playlist_free(&c->playlist);
590   c->song = NULL;
591   c->playlist.updated = TRUE;
593   mpd_sendPlaylistInfoCommand(c->connection,-1);
594   while( (entity=mpd_getNextInfoEntity(c->connection)) ) 
595     {
596       if(entity->type==MPD_INFO_ENTITY_TYPE_SONG) 
597         {
598           mpd_Song *song = mpd_songDup(entity->info.song);
600           c->playlist.list = g_list_append(c->playlist.list, (gpointer) song);
601           c->playlist.length++;
602         }
603       mpd_freeInfoEntity(entity);
604     }
605   c->playlist.id = c->status->playlist;
606   c->song = NULL;
608   /* call playlist updated callbacks */
609   mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
611   return mpdclient_finish_command(c);
614 /* update playlist (plchanges) */
615 gint 
616 mpdclient_playlist_update_changes(mpdclient_t *c)
618   mpd_InfoEntity *entity;
620   D("mpdclient_playlist_update_changes() [%lld -> %lld]\n", 
621     c->status->playlist, c->playlist.id);
623   if( MPD_ERROR(c) )
624     return -1;
626   mpd_sendPlChangesCommand(c->connection, c->playlist.id); 
628   while( (entity=mpd_getNextInfoEntity(c->connection)) != NULL   ) 
629     {
630       if(entity->type==MPD_INFO_ENTITY_TYPE_SONG) 
631         {
632           mpd_Song *song;
633           GList *item;
635           if( (song=mpd_songDup(entity->info.song)) == NULL )
636             {
637               D("song==NULL => calling mpdclient_playlist_update()\n");
638               return mpdclient_playlist_update(c);
639             }
641           item =  playlist_lookup(c, song->id);
643           if( item && item->data)
644             {
645               /* Update playlist entry */
646               mpd_freeSong((mpd_Song *) item->data);
647               item->data = song;
648               if( c->song && c->song->id == song->id )
649                 c->song = song;
650               D("Changing num %d [%d] to %s\n",
651                 song->pos, song->id, get_song_name(song));
652             }
653           else
654             {
655               /* Add a new  playlist entry */
656               D("Adding pos:%d, id;%d - %s\n",
657                 song->pos, song->id, get_song_name(song));
658               c->playlist.list = g_list_append(c->playlist.list, 
659                                           (gpointer) song);
660               c->playlist.length++;
661             }
662         }
663       mpd_freeInfoEntity(entity);      
664     }
665   mpd_finishCommand(c->connection);
666   
667   while( g_list_length(c->playlist.list) > c->status->playlistLength )
668     {
669       GList *item = g_list_last(c->playlist.list);
671       /* Remove the last playlist entry */
672       mpd_freeSong((mpd_Song *) item->data);
673       c->playlist.list = g_list_delete_link(c->playlist.list, item);
674       c->playlist.length--;      
675       D("Removed the last playlist entry\n");
676     }
678   c->playlist.id = c->status->playlist;
679   c->playlist.updated = TRUE;
681   mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL);
683   return 0;
686 mpd_Song *
687 playlist_get_song(mpdclient_t *c, gint index)
689   return (mpd_Song *) g_list_nth_data(c->playlist.list, index);
692 GList *
693 playlist_lookup(mpdclient_t *c, gint id)
695   GList *list = c->playlist.list;
697   while( list )
698     {
699       mpd_Song *song = (mpd_Song *) list->data;
700       if( song->id == id )
701         return list;
702       list=list->next;
703     }
704   return NULL;
707 mpd_Song *
708 playlist_lookup_song(mpdclient_t *c, gint id)
710   GList *list = c->playlist.list;
712   while( list )
713     {
714       mpd_Song *song = (mpd_Song *) list->data;
715       if( song->id == id )
716         return song;
717       list=list->next;
718     }
719   return NULL;
722 gint 
723 playlist_get_index(mpdclient_t *c, mpd_Song *song)
725   return g_list_index(c->playlist.list, song);
728 gint 
729 playlist_get_index_from_id(mpdclient_t *c, gint id)
731   return g_list_index(c->playlist.list, playlist_lookup_song(c, id));
734 gint
735 playlist_get_index_from_file(mpdclient_t *c, gchar *filename)
737   GList *list = c->playlist.list;
738   gint i=0;
740   while( list )
741     {
742       mpd_Song *song = (mpd_Song *) list->data;
743       if( strcmp(song->file, filename ) == 0 )  
744         return i;
745       list=list->next;
746       i++;
747     }
748   return -1;
752 /****************************************************************************/
753 /*** Filelist functions *****************************************************/
754 /****************************************************************************/
756 mpdclient_filelist_t *
757 mpdclient_filelist_free(mpdclient_filelist_t *filelist)
759   GList *list = g_list_first(filelist->list);
761   while( list!=NULL )
762     {
763       filelist_entry_t *entry = list->data;
765       if( entry->entity )
766         mpd_freeInfoEntity(entry->entity);
767       g_free(entry);
768       list=list->next;
769     }
770   g_list_free(filelist->list);
771   g_free(filelist->path);
772   filelist->path = NULL;
773   filelist->list = NULL;
774   filelist->length = 0;
775   g_free(filelist);
777   return NULL;
781 mpdclient_filelist_t *
782 mpdclient_filelist_get(mpdclient_t *c, gchar *path)
784   mpdclient_filelist_t *filelist;
785   mpd_InfoEntity *entity;
786   gchar *path_utf8 = locale_to_utf8(path);
788   mpd_sendLsInfoCommand(c->connection, path_utf8);
789   filelist = g_malloc0(sizeof(mpdclient_filelist_t));
790   if( path && path[0] && strcmp(path, "/") )
791     {
792       /* add a dummy entry for ./.. */
793       filelist_entry_t *entry = g_malloc0(sizeof(filelist_entry_t));
794       entry->entity = NULL;
795       filelist->list = g_list_append(filelist->list, (gpointer) entry);
796       filelist->length++;
797     }
799   while( (entity=mpd_getNextInfoEntity(c->connection)) ) 
800     {
801       filelist_entry_t *entry = g_malloc0(sizeof(filelist_entry_t));
802       
803       entry->entity = entity;
804       filelist->list = g_list_append(filelist->list, (gpointer) entry);
805       filelist->length++;
806     }
807   
808   if( mpdclient_finish_command(c) )
809     {
810       g_free(path_utf8);
811       return mpdclient_filelist_free(filelist);
812     }
813   
814   g_free(path_utf8);
815   filelist->path = g_strdup(path);
816   filelist->updated = TRUE;
818   return filelist;
821 mpdclient_filelist_t *
822 mpdclient_filelist_update(mpdclient_t *c, mpdclient_filelist_t *filelist)
824   if( filelist == NULL )
825     {    
826       gchar *path = g_strdup(filelist->path);
828       filelist = mpdclient_filelist_free(filelist);
829       filelist = mpdclient_filelist_get(c, path);
830       g_free(path);
831       return filelist;
832     }
833   return NULL;
836 filelist_entry_t *
837 mpdclient_filelist_find_song(mpdclient_filelist_t *fl, mpd_Song *song)
839   GList *list = g_list_first(fl->list);
841   while( list && song)
842     {
843       filelist_entry_t *entry = list->data;
844       mpd_InfoEntity *entity  = entry->entity;
846       if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG )
847         {
848           mpd_Song *song2 = entity->info.song;
850           if( strcmp(song->file, song2->file) == 0 )
851             {
852               return entry;
853             }
854         }
855       list = list->next;
856     }
857   return NULL;