diff options
Diffstat (limited to '')
-rw-r--r-- | src/mpdclient.c | 868 |
1 files changed, 868 insertions, 0 deletions
diff --git a/src/mpdclient.c b/src/mpdclient.c new file mode 100644 index 000000000..af6476fdb --- /dev/null +++ b/src/mpdclient.c @@ -0,0 +1,868 @@ +/* + * $Id$ + * + * (c) 2004 by Kalle Wallin <kaw@linux.se> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <string.h> +#include <glib.h> + +#include "config.h" +#include "ncmpc.h" +#include "support.h" +#include "mpdclient.h" +#include "options.h" + +#undef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD /* broken with song id's */ +#define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE +#define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE +#undef ENABLE_SONG_ID + +#define MPD_ERROR(c) (c==NULL || c->connection==NULL || c->connection->error) + + +/* Error callbacks */ +static gint +error_cb(mpdclient_t *c, gint error, gchar *msg) +{ + GList *list = c->error_callbacks; + + if( list==NULL ) + fprintf(stderr, "error [%d]: %s\n", error, msg); + + while(list) + { + mpdc_error_cb_t cb = list->data; + if( cb ) + cb(c, error, msg); + list=list->next; + } + mpd_clearError(c->connection); + return error; +} + +#ifdef DEBUG +#include "strfsong.h" + +static gchar * +get_song_name(mpd_Song *song) +{ + static gchar name[256]; + + strfsong(name, 256, "[%artist% - ]%title%|%file%", song); + return name; +} + +#endif + +/****************************************************************************/ +/*** mpdclient functions ****************************************************/ +/****************************************************************************/ + +gint +mpdclient_finish_command(mpdclient_t *c) +{ + mpd_finishCommand(c->connection); + + if( c->connection->error ) + { + gchar *msg = locale_to_utf8(c->connection->errorStr); + gint retval = c->connection->error; + + error_cb(c, c->connection->error, msg); + g_free(msg); + return retval; + } + + return 0; +} + +mpdclient_t * +mpdclient_new(void) +{ + mpdclient_t *c; + + c = g_malloc0(sizeof(mpdclient_t)); + + return c; +} + +mpdclient_t * +mpdclient_free(mpdclient_t *c) +{ + mpdclient_disconnect(c); + g_list_free(c->error_callbacks); + g_list_free(c->playlist_callbacks); + g_list_free(c->browse_callbacks); + g_free(c); + + return NULL; +} + +gint +mpdclient_disconnect(mpdclient_t *c) +{ + D("mpdclient_disconnect()...\n"); + if( c->connection ) + mpd_closeConnection(c->connection); + c->connection = NULL; + + if( c->status ) + mpd_freeStatus(c->status); + c->status = NULL; + + if( c->playlist.list ) + mpdclient_playlist_free(&c->playlist); + + if( c->song ) + c->song = NULL; + + return 0; +} + +gint +mpdclient_connect(mpdclient_t *c, + gchar *host, + gint port, + gfloat timeout, + gchar *password) +{ + gint retval = 0; + + /* close any open connection */ + if( c->connection ) + mpdclient_disconnect(c); + + /* connect to MPD */ + D("mpdclient_connect(%s, %d)...\n", host, port); + c->connection = mpd_newConnection(host, port, timeout); + if( c->connection->error ) + return error_cb(c, c->connection->error, c->connection->errorStr); + + /* send password */ + if( password ) + { + mpd_sendPasswordCommand(c->connection, password); + retval = mpdclient_finish_command(c); + } + + return retval; +} + +gint +mpdclient_update(mpdclient_t *c) +{ + gint retval = 0; + + if( MPD_ERROR(c) ) + return -1; + + /* free the old status */ + if( c->status ) + mpd_freeStatus(c->status); + + /* retreive new status */ + mpd_sendStatusCommand(c->connection); + c->status = mpd_getStatus(c->connection); + if( (retval=mpdclient_finish_command(c)) ) + return retval; +#ifdef DEBUG + if( c->status->error ) + D("status> %s\n", c->status->error); +#endif + + /* check if the playlist needs an update */ + if( c->playlist.id != c->status->playlist ) + { + if( c->playlist.list ) + retval = mpdclient_playlist_update_changes(c); + else + retval = mpdclient_playlist_update(c); + } + + /* update the current song */ + if( !c->song || c->status->songid != c->song->id ) + { + c->song = playlist_get_song(c, c->status->song); + } + + c->need_update = FALSE; + + return retval; +} + + +/****************************************************************************/ +/*** MPD Commands **********************************************************/ +/****************************************************************************/ + +gint +mpdclient_cmd_play(mpdclient_t *c, gint index) +{ +#ifdef ENABLE_SONG_ID + mpd_Song *song = playlist_get_song(c, index); + + if( song ) + mpd_sendPlayIdCommand(c->connection, song->id); + else + mpd_sendPlayIdCommand(c->connection, MPD_PLAY_AT_BEGINNING); +#else + mpd_sendPlayCommand(c->connection, index); +#endif + c->need_update = TRUE; + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_pause(mpdclient_t *c, gint value) +{ + mpd_sendPauseCommand(c->connection, value); + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_stop(mpdclient_t *c) +{ + mpd_sendStopCommand(c->connection); + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_next(mpdclient_t *c) +{ + mpd_sendNextCommand(c->connection); + c->need_update = TRUE; + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_prev(mpdclient_t *c) +{ + mpd_sendPrevCommand(c->connection); + c->need_update = TRUE; + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_seek(mpdclient_t *c, gint id, gint pos) +{ + mpd_sendSeekIdCommand(c->connection, id, pos); + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_shuffle(mpdclient_t *c) +{ + mpd_sendShuffleCommand(c->connection); + c->need_update = TRUE; + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_clear(mpdclient_t *c) +{ + gint retval = 0; + + mpd_sendClearCommand(c->connection); + retval = mpdclient_finish_command(c); + /* call playlist updated callback */ + mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL); + c->need_update = TRUE; + return retval; +} + +gint +mpdclient_cmd_repeat(mpdclient_t *c, gint value) +{ + mpd_sendRepeatCommand(c->connection, value); + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_random(mpdclient_t *c, gint value) +{ + mpd_sendRandomCommand(c->connection, value); + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_crossfade(mpdclient_t *c, gint value) +{ + mpd_sendCrossfadeCommand(c->connection, value); + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_db_update(mpdclient_t *c) +{ + mpd_sendUpdateCommand(c->connection); + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_volume(mpdclient_t *c, gint value) +{ + mpd_sendSetvolCommand(c->connection, value); + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_add(mpdclient_t *c, mpd_Song *song) +{ + gint retval = 0; + + if( !song || !song->file ) + return -1; + + /* send the add command to mpd */ + mpd_sendAddCommand(c->connection, song->file); + if( (retval=mpdclient_finish_command(c)) ) + return retval; + +#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD + /* add the song to playlist */ + c->playlist.list = g_list_append(c->playlist.list, mpd_songDup(song)); + c->playlist.length++; + + /* increment the playlist id, so we dont retrives a new playlist */ + c->playlist.id++; + + /* call playlist updated callback */ + mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song); +#else + c->need_update = TRUE; +#endif + + return 0; +} + +gint +mpdclient_cmd_delete(mpdclient_t *c, gint index) +{ + gint retval = 0; + mpd_Song *song = playlist_get_song(c, index); + + if( !song ) + return -1; + + /* send the delete command to mpd */ +#ifdef ENABLE_SONG_ID + mpd_sendDeleteIdCommand(c->connection, song->id); +#else + mpd_sendDeleteCommand(c->connection, index); +#endif + if( (retval=mpdclient_finish_command(c)) ) + return retval; + +#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE + /* increment the playlist id, so we dont retrive a new playlist */ + c->playlist.id++; + + /* remove the song from the playlist */ + c->playlist.list = g_list_remove(c->playlist.list, (gpointer) song); + c->playlist.length = g_list_length(c->playlist.list); + + /* call playlist updated callback */ + mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song); + + /* remove references to the song */ + if( c->song == song ) + { + c->song = NULL; + c->need_update = TRUE; + } + + /* free song */ + mpd_freeSong(song); + +#else + c->need_update = TRUE; +#endif + + return 0; +} + +gint +mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index) +{ + gint retval, index1, index2; + GList *item1, *item2; + gpointer data1, data2; + mpd_Song *song1, *song2; + + if( old_index==new_index || new_index<0 || new_index>=c->playlist.length ) + return -1; + + song1 = playlist_get_song(c, old_index); + song2 = playlist_get_song(c, new_index); + + /* send the move command to mpd */ +#ifdef ENABLE_SONG_ID + mpd_sendMoveIdCommand(c->connection, song1->id, song2->id); +#else + mpd_sendMoveCommand(c->connection, old_index, new_index); +#endif + if( (retval=mpdclient_finish_command(c)) ) + return retval; + +#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE + index1 = MIN(old_index, new_index); + index2 = MAX(old_index, new_index); + item1 = g_list_nth(c->playlist.list, index1); + item2 = g_list_nth(c->playlist.list, index2); + data1 = item1->data; + data2 = item2->data; + + /* move the second item */ + c->playlist.list = g_list_remove(c->playlist.list, data2); + c->playlist.list = g_list_insert_before(c->playlist.list, item1, data2); + + /* move the first item */ + if( index2-index1 >1 ) + { + item2 = g_list_nth(c->playlist.list, index2); + c->playlist.list = g_list_remove(c->playlist.list, data1); + c->playlist.list = g_list_insert_before(c->playlist.list, item2, data1); + } + + /* increment the playlist id, so we dont retrives a new playlist */ + c->playlist.id++; + + /* call playlist updated callback */ + mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index); +#else + c->need_update = TRUE; +#endif + + return 0; +} + +gint +mpdclient_cmd_save_playlist(mpdclient_t *c, gchar *filename) +{ + gint retval = 0; + gchar *filename_utf8 = locale_to_utf8(filename); + + mpd_sendSaveCommand(c->connection, filename_utf8); + if( (retval=mpdclient_finish_command(c)) == 0 ) + mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL); + g_free(filename_utf8); + return retval; +} + +gint +mpdclient_cmd_load_playlist(mpdclient_t *c, gchar *filename_utf8) +{ + mpd_sendLoadCommand(c->connection, filename_utf8); + c->need_update = TRUE; + return mpdclient_finish_command(c); +} + +gint +mpdclient_cmd_delete_playlist(mpdclient_t *c, gchar *filename_utf8) +{ + gint retval = 0; + + mpd_sendRmCommand(c->connection, filename_utf8); + if( (retval=mpdclient_finish_command(c)) == 0 ) + mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL); + return retval; +} + + +/****************************************************************************/ +/*** Callback managment functions *******************************************/ +/****************************************************************************/ +static void +do_list_callbacks(mpdclient_t *c, GList *list, gint event, gpointer data) +{ + while(list) + { + mpdc_list_cb_t fn = list->data; + + fn(c, event, data); + list=list->next; + } +} + +void +mpdclient_playlist_callback(mpdclient_t *c, int event, gpointer data) +{ + do_list_callbacks(c, c->playlist_callbacks, event, data); +} + +void +mpdclient_install_playlist_callback(mpdclient_t *c,mpdc_list_cb_t cb) +{ + c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb); +} + +void +mpdclient_remove_playlist_callback(mpdclient_t *c, mpdc_list_cb_t cb) +{ + c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb); +} + +void +mpdclient_browse_callback(mpdclient_t *c, int event, gpointer data) +{ + do_list_callbacks(c, c->browse_callbacks, event, data); +} + + +void +mpdclient_install_browse_callback(mpdclient_t *c,mpdc_list_cb_t cb) +{ + c->browse_callbacks = g_list_append(c->browse_callbacks, cb); +} + +void +mpdclient_remove_browse_callback(mpdclient_t *c, mpdc_list_cb_t cb) +{ + c->browse_callbacks = g_list_remove(c->browse_callbacks, cb); +} + +void +mpdclient_install_error_callback(mpdclient_t *c, mpdc_error_cb_t cb) +{ + c->error_callbacks = g_list_append(c->error_callbacks, cb); +} + +void +mpdclient_remove_error_callback(mpdclient_t *c, mpdc_error_cb_t cb) +{ + c->error_callbacks = g_list_remove(c->error_callbacks, cb); +} + +/****************************************************************************/ +/*** Playlist managment functions *******************************************/ +/****************************************************************************/ + +gint +mpdclient_playlist_free(mpdclient_playlist_t *playlist) +{ + GList *list = g_list_first(playlist->list); + + while(list) + { + mpd_Song *song = (mpd_Song *) list->data; + mpd_freeSong(song); + list=list->next; + } + g_list_free(playlist->list); + playlist->list = NULL; + playlist->length = 0; + return 0; +} + +/* update playlist */ +gint +mpdclient_playlist_update(mpdclient_t *c) +{ + mpd_InfoEntity *entity; + + D("mpdclient_playlist_update() [%lld]\n", c->status->playlist); + + if( MPD_ERROR(c) ) + return -1; + + if( c->playlist.list ) + mpdclient_playlist_free(&c->playlist); + + c->song = NULL; + c->playlist.updated = TRUE; + + mpd_sendPlaylistInfoCommand(c->connection,-1); + while( (entity=mpd_getNextInfoEntity(c->connection)) ) + { + if(entity->type==MPD_INFO_ENTITY_TYPE_SONG) + { + mpd_Song *song = mpd_songDup(entity->info.song); + + c->playlist.list = g_list_append(c->playlist.list, (gpointer) song); + c->playlist.length++; + } + mpd_freeInfoEntity(entity); + } + c->playlist.id = c->status->playlist; + c->song = NULL; + + /* call playlist updated callbacks */ + mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL); + + return mpdclient_finish_command(c); +} + +/* update playlist (plchanges) */ +gint +mpdclient_playlist_update_changes(mpdclient_t *c) +{ + mpd_InfoEntity *entity; + + D("mpdclient_playlist_update_changes() [%lld -> %lld]\n", + c->status->playlist, c->playlist.id); + + if( MPD_ERROR(c) ) + return -1; + + mpd_sendPlChangesCommand(c->connection, c->playlist.id); + + while( (entity=mpd_getNextInfoEntity(c->connection)) != NULL ) + { + if(entity->type==MPD_INFO_ENTITY_TYPE_SONG) + { + mpd_Song *song; + GList *item; + + if( (song=mpd_songDup(entity->info.song)) == NULL ) + { + D("song==NULL => calling mpdclient_playlist_update()\n"); + return mpdclient_playlist_update(c); + } + + item = playlist_lookup(c, song->id); + + if( item && item->data) + { + /* Update playlist entry */ + mpd_freeSong((mpd_Song *) item->data); + item->data = song; + if( c->song && c->song->id == song->id ) + c->song = song; + D("Changing num %d [%d] to %s\n", + song->pos, song->id, get_song_name(song)); + } + else + { + /* Add a new playlist entry */ + D("Adding pos:%d, id;%d - %s\n", + song->pos, song->id, get_song_name(song)); + c->playlist.list = g_list_append(c->playlist.list, + (gpointer) song); + c->playlist.length++; + } + } + mpd_freeInfoEntity(entity); + } + mpd_finishCommand(c->connection); + + while( g_list_length(c->playlist.list) > c->status->playlistLength ) + { + GList *item = g_list_last(c->playlist.list); + + /* Remove the last playlist entry */ + mpd_freeSong((mpd_Song *) item->data); + c->playlist.list = g_list_delete_link(c->playlist.list, item); + c->playlist.length--; + D("Removed the last playlist entry\n"); + } + + c->playlist.id = c->status->playlist; + c->playlist.updated = TRUE; + + mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL); + + return 0; +} + +mpd_Song * +playlist_get_song(mpdclient_t *c, gint index) +{ + return (mpd_Song *) g_list_nth_data(c->playlist.list, index); +} + +GList * +playlist_lookup(mpdclient_t *c, gint id) +{ + GList *list = c->playlist.list; + + while( list ) + { + mpd_Song *song = (mpd_Song *) list->data; + if( song->id == id ) + return list; + list=list->next; + } + return NULL; +} + +mpd_Song * +playlist_lookup_song(mpdclient_t *c, gint id) +{ + GList *list = c->playlist.list; + + while( list ) + { + mpd_Song *song = (mpd_Song *) list->data; + if( song->id == id ) + return song; + list=list->next; + } + return NULL; +} + +gint +playlist_get_index(mpdclient_t *c, mpd_Song *song) +{ + return g_list_index(c->playlist.list, song); +} + +gint +playlist_get_index_from_id(mpdclient_t *c, gint id) +{ + return g_list_index(c->playlist.list, playlist_lookup_song(c, id)); +} + +gint +playlist_get_index_from_file(mpdclient_t *c, gchar *filename) +{ + GList *list = c->playlist.list; + gint i=0; + + while( list ) + { + mpd_Song *song = (mpd_Song *) list->data; + if( strcmp(song->file, filename ) == 0 ) + return i; + list=list->next; + i++; + } + return -1; +} + + +/****************************************************************************/ +/*** Filelist functions *****************************************************/ +/****************************************************************************/ + +mpdclient_filelist_t * +mpdclient_filelist_free(mpdclient_filelist_t *filelist) +{ + GList *list = g_list_first(filelist->list); + + while( list!=NULL ) + { + filelist_entry_t *entry = list->data; + + if( entry->entity ) + mpd_freeInfoEntity(entry->entity); + g_free(entry); + list=list->next; + } + g_list_free(filelist->list); + g_free(filelist->path); + filelist->path = NULL; + filelist->list = NULL; + filelist->length = 0; + g_free(filelist); + + return NULL; +} + + +mpdclient_filelist_t * +mpdclient_filelist_get(mpdclient_t *c, gchar *path) +{ + mpdclient_filelist_t *filelist; + mpd_InfoEntity *entity; + gchar *path_utf8 = locale_to_utf8(path); + + mpd_sendLsInfoCommand(c->connection, path_utf8); + filelist = g_malloc0(sizeof(mpdclient_filelist_t)); + if( path && path[0] && strcmp(path, "/") ) + { + /* add a dummy entry for ./.. */ + filelist_entry_t *entry = g_malloc0(sizeof(filelist_entry_t)); + entry->entity = NULL; + filelist->list = g_list_append(filelist->list, (gpointer) entry); + filelist->length++; + } + + while( (entity=mpd_getNextInfoEntity(c->connection)) ) + { + filelist_entry_t *entry = g_malloc0(sizeof(filelist_entry_t)); + + entry->entity = entity; + filelist->list = g_list_append(filelist->list, (gpointer) entry); + filelist->length++; + } + + if( mpdclient_finish_command(c) ) + { + g_free(path_utf8); + return mpdclient_filelist_free(filelist); + } + + g_free(path_utf8); + filelist->path = g_strdup(path); + filelist->updated = TRUE; + + return filelist; +} + +mpdclient_filelist_t * +mpdclient_filelist_update(mpdclient_t *c, mpdclient_filelist_t *filelist) +{ + if( filelist == NULL ) + { + gchar *path = g_strdup(filelist->path); + + filelist = mpdclient_filelist_free(filelist); + filelist = mpdclient_filelist_get(c, path); + g_free(path); + return filelist; + } + return NULL; +} + +filelist_entry_t * +mpdclient_filelist_find_song(mpdclient_filelist_t *fl, mpd_Song *song) +{ + GList *list = g_list_first(fl->list); + + while( list && song) + { + filelist_entry_t *entry = list->data; + mpd_InfoEntity *entity = entry->entity; + + if( entity && entity->type==MPD_INFO_ENTITY_TYPE_SONG ) + { + mpd_Song *song2 = entity->info.song; + + if( strcmp(song->file, song2->file) == 0 ) + { + return entry; + } + } + list = list->next; + } + return NULL; +} + + + + + + + + + + |