X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fmpdclient.c;h=d60436946a378753538c1711cb653d0ce4bc9c16;hb=f46b959ea0ef555e27ef80c764ef808096f32bb3;hp=f8f5ba72c42fff3c8f20503aaba206e07792b86f;hpb=a00c1bb99e0706ef8a59f14baae76f194f27d7db;p=ncmpc.git diff --git a/src/mpdclient.c b/src/mpdclient.c index f8f5ba7..d604369 100644 --- a/src/mpdclient.c +++ b/src/mpdclient.c @@ -18,47 +18,58 @@ */ #include "mpdclient.h" -#include "screen_utils.h" +#include "filelist.h" +#include "screen_client.h" #include "config.h" #include "options.h" #include "strfsong.h" +#include "utils.h" + +#include #include #include #include #include -#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 -#define ENABLE_SONG_ID -#define ENABLE_PLCHANGES #define BUFSIZE 1024 -#define MPD_ERROR(c) (c==NULL || c->connection==NULL || c->connection->error) - -/* from utils.c */ -extern GList *string_list_free(GList *string_list); - +static bool +MPD_ERROR(const struct mpdclient *client) +{ + return client->connection == NULL || + mpd_connection_get_error(client->connection) != MPD_ERROR_SUCCESS; +} /* filelist sorting functions */ static gint -compare_filelistentry_dir(gconstpointer filelist_entry1, +compare_filelistentry(gconstpointer filelist_entry1, gconstpointer filelist_entry2) { - const mpd_InfoEntity *e1, *e2; + const struct mpd_entity *e1, *e2; int n = 0; - e1 = ((const filelist_entry_t *)filelist_entry1)->entity; - e2 = ((const filelist_entry_t *)filelist_entry2)->entity; - - if (e1 && e2 && - e1->type == MPD_INFO_ENTITY_TYPE_DIRECTORY && - e2->type == MPD_INFO_ENTITY_TYPE_DIRECTORY) - n = g_utf8_collate(e1->info.directory->path, - e2->info.directory->path); - + e1 = ((const struct filelist_entry *)filelist_entry1)->entity; + e2 = ((const struct filelist_entry *)filelist_entry2)->entity; + + if (e1 != NULL && e2 != NULL && + mpd_entity_get_type(e1) == mpd_entity_get_type(e2)) { + switch (mpd_entity_get_type(e1)) { + case MPD_ENTITY_TYPE_UNKNOWN: + break; + case MPD_ENTITY_TYPE_DIRECTORY: + n = g_utf8_collate(mpd_directory_get_path(mpd_entity_get_directory(e1)), + mpd_directory_get_path(mpd_entity_get_directory(e2))); + break; + case MPD_ENTITY_TYPE_SONG: + break; + case MPD_ENTITY_TYPE_PLAYLIST: + n = g_utf8_collate(mpd_playlist_get_path(mpd_entity_get_playlist(e1)), + mpd_playlist_get_path(mpd_entity_get_playlist(e2))); + } + } return n; } @@ -67,18 +78,18 @@ gint compare_filelistentry_format(gconstpointer filelist_entry1, gconstpointer filelist_entry2) { - const mpd_InfoEntity *e1, *e2; + const struct mpd_entity *e1, *e2; char key1[BUFSIZE], key2[BUFSIZE]; int n = 0; - e1 = ((const filelist_entry_t *)filelist_entry1)->entity; - e2 = ((const filelist_entry_t *)filelist_entry2)->entity; + e1 = ((const struct filelist_entry *)filelist_entry1)->entity; + e2 = ((const struct filelist_entry *)filelist_entry2)->entity; if (e1 && e2 && - e1->type == MPD_INFO_ENTITY_TYPE_SONG && - e2->type == MPD_INFO_ENTITY_TYPE_SONG) { - strfsong(key1, BUFSIZE, options.list_format, e1->info.song); - strfsong(key2, BUFSIZE, options.list_format, e2->info.song); + mpd_entity_get_type(e1) == MPD_ENTITY_TYPE_SONG && + mpd_entity_get_type(e2) == MPD_ENTITY_TYPE_SONG) { + strfsong(key1, BUFSIZE, options.list_format, mpd_entity_get_song(e1)); + strfsong(key2, BUFSIZE, options.list_format, mpd_entity_get_song(e2)); n = strcmp(key1,key2); } @@ -86,159 +97,166 @@ compare_filelistentry_format(gconstpointer filelist_entry1, } -/* 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 & 0xFF), 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; -} - - /****************************************************************************/ /*** mpdclient functions ****************************************************/ /****************************************************************************/ gint -mpdclient_finish_command(mpdclient_t *c) +mpdclient_handle_error(struct mpdclient *c) { - mpd_finishCommand(c->connection); + enum mpd_error error = mpd_connection_get_error(c->connection); - if (c->connection->error) { - gint error = c->connection->error; + assert(error != MPD_ERROR_SUCCESS); - if (error == MPD_ERROR_ACK && - c->connection->errorCode == MPD_ACK_ERROR_PERMISSION && - screen_auth(c) == 0) - return 0; + if (error == MPD_ERROR_SERVER && + mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION && + screen_auth(c)) + return 0; - if (error == MPD_ERROR_ACK) - error = error | (c->connection->errorCode << 8); + if (error == MPD_ERROR_SERVER) + error = error | (mpd_connection_get_server_error(c->connection) << 8); - error_cb(c, error, c->connection->errorStr); - return error; - } + mpdclient_ui_error(mpd_connection_get_error_message(c->connection)); - return 0; + if (!mpd_connection_clear_error(c->connection)) + mpdclient_disconnect(c); + + return error; } -mpdclient_t * +static gint +mpdclient_finish_command(struct mpdclient *c) +{ + return mpd_response_finish(c->connection) + ? 0 : mpdclient_handle_error(c); +} + +struct mpdclient * mpdclient_new(void) { - mpdclient_t *c; + struct mpdclient *c; - c = g_malloc0(sizeof(mpdclient_t)); + c = g_new0(struct mpdclient, 1); playlist_init(&c->playlist); + c->volume = -1; + c->events = 0; return c; } void -mpdclient_free(mpdclient_t *c) +mpdclient_free(struct mpdclient *c) { mpdclient_disconnect(c); mpdclient_playlist_free(&c->playlist); - g_list_free(c->error_callbacks); - g_list_free(c->playlist_callbacks); - g_list_free(c->browse_callbacks); g_free(c); } -gint -mpdclient_disconnect(mpdclient_t *c) +void +mpdclient_disconnect(struct mpdclient *c) { if (c->connection) - mpd_closeConnection(c->connection); + mpd_connection_free(c->connection); c->connection = NULL; if (c->status) - mpd_freeStatus(c->status); + mpd_status_free(c->status); c->status = NULL; playlist_clear(&c->playlist); if (c->song) c->song = NULL; - - return 0; } -gint -mpdclient_connect(mpdclient_t *c, - gchar *host, +bool +mpdclient_connect(struct mpdclient *c, + const gchar *host, gint port, gfloat _timeout, - gchar *password) + const gchar *password) { - gint retval = 0; - /* close any open connection */ if( c->connection ) mpdclient_disconnect(c); /* connect to MPD */ - c->connection = mpd_newConnection(host, port, _timeout); - if( c->connection->error ) - return error_cb(c, c->connection->error, - c->connection->errorStr); + c->connection = mpd_connection_new(host, port, _timeout * 1000); + if (c->connection == NULL) + g_error("Out of memory"); + + if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) { + mpdclient_handle_error(c); + mpdclient_disconnect(c); + return false; + } /* send password */ - if( password ) { - mpd_sendPasswordCommand(c->connection, password); - retval = mpdclient_finish_command(c); + if (password != NULL && !mpd_run_password(c->connection, password)) { + mpdclient_handle_error(c); + mpdclient_disconnect(c); + return false; } - c->need_update = TRUE; - return retval; + return true; } -gint -mpdclient_update(mpdclient_t *c) +bool +mpdclient_update(struct mpdclient *c) { - gint retval = 0; + bool retval; + + c->volume = -1; if (MPD_ERROR(c)) - return -1; + return false; + + /* always announce these options as long as we don't have real + "idle" support */ + c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS; /* free the old status */ if (c->status) - mpd_freeStatus(c->status); + mpd_status_free(c->status); /* retrieve new status */ - mpd_sendStatusCommand(c->connection); - c->status = mpd_getStatus(c->connection); - if ((retval=mpdclient_finish_command(c))) - return retval; + c->status = mpd_run_status(c->connection); + if (c->status == NULL) + return mpdclient_handle_error(c) == 0; + + if (c->update_id != mpd_status_get_update_id(c->status)) { + c->events |= MPD_IDLE_UPDATE; + + if (c->update_id > 0) + c->events |= MPD_IDLE_DATABASE; + } + + c->update_id = mpd_status_get_update_id(c->status); + + if (c->volume != mpd_status_get_volume(c->status)) + c->events |= MPD_IDLE_MIXER; + + c->volume = mpd_status_get_volume(c->status); /* check if the playlist needs an update */ - if (c->playlist.id != c->status->playlist) { - if (playlist_is_empty(&c->playlist)) + if (c->playlist.version != mpd_status_get_queue_version(c->status)) { + c->events |= MPD_IDLE_PLAYLIST; + + if (!playlist_is_empty(&c->playlist)) retval = mpdclient_playlist_update_changes(c); else retval = mpdclient_playlist_update(c); - } + } else + retval = true; /* update the current song */ - if (!c->song || c->status->songid != c->song->id) { - c->song = playlist_get_song(c, c->status->song); + if (!c->song || mpd_status_get_song_id(c->status)) { + c->song = playlist_get_song(&c->playlist, + mpd_status_get_song_pos(c->status)); } - c->need_update = FALSE; - return retval; } @@ -248,243 +266,338 @@ mpdclient_update(mpdclient_t *c) /****************************************************************************/ gint -mpdclient_cmd_play(mpdclient_t *c, gint idx) +mpdclient_cmd_play(struct mpdclient *c, gint idx) { -#ifdef ENABLE_SONG_ID - struct mpd_song *song = playlist_get_song(c, idx); + const struct mpd_song *song = playlist_get_song(&c->playlist, idx); + + if (MPD_ERROR(c)) + return -1; if (song) - mpd_sendPlayIdCommand(c->connection, song->id); + mpd_send_play_id(c->connection, mpd_song_get_id(song)); else - mpd_sendPlayIdCommand(c->connection, MPD_PLAY_AT_BEGINNING); -#else - mpd_sendPlayCommand(c->connection, idx); -#endif - c->need_update = TRUE; - return mpdclient_finish_command(c); -} + mpd_send_play(c->connection); -gint -mpdclient_cmd_pause(mpdclient_t *c, gint value) -{ - mpd_sendPauseCommand(c->connection, value); return mpdclient_finish_command(c); } gint -mpdclient_cmd_crop(mpdclient_t *c) +mpdclient_cmd_crop(struct mpdclient *c) { - mpd_Status *status; - int length; + struct mpd_status *status; + bool playing; + int length, current; - mpd_sendStatusCommand(c->connection); - status = mpd_getStatus(c->connection); - length = status->playlistLength - 1; + if (MPD_ERROR(c)) + return -1; - if (length <= 0) { - mpd_freeStatus(status); - } else if (status->state == 3 || status->state == 2) { - /* If playing or paused */ + status = mpd_run_status(c->connection); + if (status == NULL) + return mpdclient_handle_error(c); - mpd_sendCommandListBegin( c->connection ); + playing = mpd_status_get_state(status) == MPD_STATE_PLAY || + mpd_status_get_state(status) == MPD_STATE_PAUSE; + length = mpd_status_get_queue_length(status); + current = mpd_status_get_song_pos(status); - while (length >= 0) { - if (length != status->song) - mpd_sendDeleteCommand(c->connection, length); + mpd_status_free(status); - length--; - } + if (!playing || length < 2) + return 0; - mpd_sendCommandListEnd(c->connection); - mpd_freeStatus(status); - } else { - mpd_freeStatus(status); - } + mpd_command_list_begin(c->connection, false); - return mpdclient_finish_command(c); -} + while (--length >= 0) + if (length != current) + mpd_send_delete(c->connection, length); -gint -mpdclient_cmd_stop(mpdclient_t *c) -{ - mpd_sendStopCommand(c->connection); - return mpdclient_finish_command(c); -} + mpd_command_list_end(c->connection); -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) +mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end) { - mpd_sendPrevCommand(c->connection); - c->need_update = TRUE; + mpd_send_shuffle_range(c->connection, start, end); return mpdclient_finish_command(c); } gint -mpdclient_cmd_seek(mpdclient_t *c, gint id, gint pos) +mpdclient_cmd_clear(struct mpdclient *c) { - mpd_sendSeekIdCommand(c->connection, id, pos); - return mpdclient_finish_command(c); -} + gint retval = 0; -gint -mpdclient_cmd_shuffle(mpdclient_t *c) -{ - mpd_sendShuffleCommand(c->connection); - c->need_update = TRUE; - return mpdclient_finish_command(c); -} + if (MPD_ERROR(c)) + return -1; -gint -mpdclient_cmd_shuffle_range(mpdclient_t *c, guint start, guint end) -{ - mpd_sendShuffleRangeCommand(c->connection, start, end); - c->need_update = TRUE; - return mpdclient_finish_command(c); -} + mpd_send_clear(c->connection); + retval = mpdclient_finish_command(c); -gint -mpdclient_cmd_clear(mpdclient_t *c) -{ - gint retval = 0; + if (retval) + c->events |= MPD_IDLE_PLAYLIST; - 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) +mpdclient_cmd_volume(struct mpdclient *c, gint value) { - mpd_sendRepeatCommand(c->connection, value); - return mpdclient_finish_command(c); -} + if (MPD_ERROR(c)) + return -1; -gint -mpdclient_cmd_random(mpdclient_t *c, gint value) -{ - mpd_sendRandomCommand(c->connection, value); + mpd_send_set_volume(c->connection, value); return mpdclient_finish_command(c); } -gint -mpdclient_cmd_crossfade(mpdclient_t *c, gint value) +gint mpdclient_cmd_volume_up(struct mpdclient *c) { - mpd_sendCrossfadeCommand(c->connection, value); - return mpdclient_finish_command(c); + if (MPD_ERROR(c)) + return -1; + + if (c->status == NULL || + mpd_status_get_volume(c->status) == -1) + return 0; + + if (c->volume < 0) + c->volume = mpd_status_get_volume(c->status); + + if (c->volume >= 100) + return 0; + + return mpdclient_cmd_volume(c, ++c->volume); } -gint -mpdclient_cmd_db_update(mpdclient_t *c, gchar *path) +gint mpdclient_cmd_volume_down(struct mpdclient *c) { - mpd_sendUpdateCommand(c->connection, path ? path : ""); - return mpdclient_finish_command(c); + if (MPD_ERROR(c)) + return -1; + + if (c->status == NULL || mpd_status_get_volume(c->status) < 0) + return 0; + + if (c->volume < 0) + c->volume = mpd_status_get_volume(c->status); + + if (c->volume <= 0) + return 0; + + return mpdclient_cmd_volume(c, --c->volume); } gint -mpdclient_cmd_volume(mpdclient_t *c, gint value) +mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8) { - mpd_sendSetvolCommand(c->connection, value); + if (MPD_ERROR(c)) + return -1; + + mpd_send_add(c->connection, path_utf8); return mpdclient_finish_command(c); } gint -mpdclient_cmd_add_path(mpdclient_t *c, gchar *path_utf8) +mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song) { - mpd_sendAddCommand(c->connection, path_utf8); - return mpdclient_finish_command(c); + struct mpd_status *status; + struct mpd_song *new_song; + + assert(c != NULL); + assert(song != NULL); + + if (MPD_ERROR(c) || c->status == NULL) + return -1; + + /* send the add command to mpd; at the same time, get the new + status (to verify the new playlist id) and the last song + (we hope that's the song we just added) */ + + if (!mpd_command_list_begin(c->connection, true) || + !mpd_send_add(c->connection, mpd_song_get_uri(song)) || + !mpd_send_status(c->connection) || + !mpd_send_get_queue_song_pos(c->connection, + playlist_length(&c->playlist)) || + !mpd_command_list_end(c->connection) || + !mpd_response_next(c->connection)) + return mpdclient_handle_error(c); + + c->events |= MPD_IDLE_PLAYLIST; + + status = mpd_recv_status(c->connection); + if (status != NULL) { + if (c->status != NULL) + mpd_status_free(c->status); + c->status = status; + } + + if (!mpd_response_next(c->connection)) + return mpdclient_handle_error(c); + + new_song = mpd_recv_song(c->connection); + if (!mpd_response_finish(c->connection) || new_song == NULL) { + if (new_song != NULL) + mpd_song_free(new_song); + + return mpd_connection_clear_error(c->connection) + ? 0 : mpdclient_handle_error(c); + } + + if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) + 1 && + mpd_status_get_queue_version(status) == c->playlist.version + 1) { + /* the cheap route: match on the new playlist length + and its version, we can keep our local playlist + copy in sync */ + c->playlist.version = mpd_status_get_queue_version(status); + + /* the song we just received has the correct id; + append it to the local playlist */ + playlist_append(&c->playlist, new_song); + } + + mpd_song_free(new_song); + + return -0; } gint -mpdclient_cmd_add(mpdclient_t *c, struct mpd_song *song) +mpdclient_cmd_delete(struct mpdclient *c, gint idx) { - gint retval = 0; + const struct mpd_song *song; + struct mpd_status *status; - if( !song || !song->file ) + if (MPD_ERROR(c) || c->status == NULL) return -1; - /* send the add command to mpd */ - mpd_sendAddCommand(c->connection, song->file); - if( (retval=mpdclient_finish_command(c)) ) - return retval; + if (idx < 0 || (guint)idx >= playlist_length(&c->playlist)) + return -1; -#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD - /* add the song to playlist */ - playlist_append(&c->playlist, song); + song = playlist_get(&c->playlist, idx); - /* increment the playlist id, so we don't retrieve a new playlist */ - c->playlist.id++; + /* send the delete command to mpd; at the same time, get the + new status (to verify the playlist id) */ - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song); -#else - c->need_update = TRUE; -#endif + if (!mpd_command_list_begin(c->connection, false) || + !mpd_send_delete_id(c->connection, mpd_song_get_id(song)) || + !mpd_send_status(c->connection) || + !mpd_command_list_end(c->connection)) + return mpdclient_handle_error(c); + + c->events |= MPD_IDLE_PLAYLIST; + + status = mpd_recv_status(c->connection); + if (status != NULL) { + if (c->status != NULL) + mpd_status_free(c->status); + c->status = status; + } + + if (!mpd_response_finish(c->connection)) + return mpdclient_handle_error(c); + + if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 && + mpd_status_get_queue_version(status) == c->playlist.version + 1) { + /* the cheap route: match on the new playlist length + and its version, we can keep our local playlist + copy in sync */ + c->playlist.version = mpd_status_get_queue_version(status); + + /* remove the song from the local playlist */ + playlist_remove(&c->playlist, idx); + + /* remove references to the song */ + if (c->song == song) + c->song = NULL; + } + + return 0; +} + +/** + * Fallback for mpdclient_cmd_delete_range() on MPD older than 0.16. + * It emulates the "delete range" command with a list of simple + * "delete" commands. + */ +static gint +mpdclient_cmd_delete_range_fallback(struct mpdclient *c, + unsigned start, unsigned end) +{ + if (!mpd_command_list_begin(c->connection, false)) + return mpdclient_handle_error(c); + + for (; start < end; --end) + mpd_send_delete(c->connection, start); + + if (!mpd_command_list_end(c->connection) || + !mpd_response_finish(c->connection)) + return mpdclient_handle_error(c); return 0; } gint -mpdclient_cmd_delete(mpdclient_t *c, gint idx) +mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end) { - gint retval = 0; - struct mpd_song *song; + struct mpd_status *status; - if (idx < 0 || (guint)idx >= playlist_length(&c->playlist)) + if (MPD_ERROR(c)) return -1; - song = playlist_get(&c->playlist, idx); + if (mpd_connection_cmp_server_version(c->connection, 0, 16, 0) < 0) + return mpdclient_cmd_delete_range_fallback(c, start, end); - /* send the delete command to mpd */ -#ifdef ENABLE_SONG_ID - mpd_sendDeleteIdCommand(c->connection, song->id); -#else - mpd_sendDeleteCommand(c->connection, idx); -#endif - if( (retval=mpdclient_finish_command(c)) ) - return retval; + /* MPD 0.16 supports "delete" with a range argument */ -#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE - /* increment the playlist id, so we don't retrieve a new playlist */ - c->playlist.id++; + /* send the delete command to mpd; at the same time, get the + new status (to verify the playlist id) */ - /* remove the song from the playlist */ - playlist_remove_reuse(&c->playlist, idx); + if (!mpd_command_list_begin(c->connection, false) || + !mpd_send_delete_range(c->connection, start, end) || + !mpd_send_status(c->connection) || + !mpd_command_list_end(c->connection)) + return mpdclient_handle_error(c); - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song); + c->events |= MPD_IDLE_PLAYLIST; - /* remove references to the song */ - if (c->song == song) { - c->song = NULL; - c->need_update = TRUE; + status = mpd_recv_status(c->connection); + if (status != NULL) { + if (c->status != NULL) + mpd_status_free(c->status); + c->status = status; } - mpd_freeSong(song); + if (!mpd_response_finish(c->connection)) + return mpdclient_handle_error(c); -#else - c->need_update = TRUE; -#endif + if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - (end - start) && + mpd_status_get_queue_version(status) == c->playlist.version + 1) { + /* the cheap route: match on the new playlist length + and its version, we can keep our local playlist + copy in sync */ + c->playlist.version = mpd_status_get_queue_version(status); + + /* remove the song from the local playlist */ + while (end > start) { + --end; + + /* remove references to the song */ + if (c->song == playlist_get(&c->playlist, end)) + c->song = NULL; + + playlist_remove(&c->playlist, end); + } + } return 0; } gint -mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index) +mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index) { gint n; struct mpd_song *song1, *song2; + if (MPD_ERROR(c)) + return -1; + if (old_index == new_index || new_index < 0 || (guint)new_index >= c->playlist.list->len) return -1; @@ -493,11 +606,8 @@ mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index) song2 = playlist_get(&c->playlist, new_index); /* send the move command to mpd */ -#ifdef ENABLE_SONG_ID - mpd_sendSwapIdCommand(c->connection, song1->id, song2->id); -#else - mpd_sendMoveCommand(c->connection, old_index, new_index); -#endif + mpd_send_swap_id(c->connection, + mpd_song_get_id(song1), mpd_song_get_id(song2)); if( (n=mpdclient_finish_command(c)) ) return n; @@ -506,111 +616,53 @@ mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index) playlist_swap(&c->playlist, old_index, new_index); /* increment the playlist id, so we don't retrieve a new playlist */ - c->playlist.id++; - -#else - c->need_update = TRUE; + c->playlist.version++; #endif - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index); + c->events |= MPD_IDLE_PLAYLIST; return 0; } gint -mpdclient_cmd_save_playlist(mpdclient_t *c, gchar *filename_utf8) +mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8) { gint retval = 0; - mpd_sendSaveCommand(c->connection, filename_utf8); - if ((retval = mpdclient_finish_command(c)) == 0) - mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL); + if (MPD_ERROR(c)) + return -1; + + mpd_send_save(c->connection, filename_utf8); + if ((retval = mpdclient_finish_command(c)) == 0) { + c->events |= MPD_IDLE_STORED_PLAYLIST; + } + return retval; } gint -mpdclient_cmd_load_playlist(mpdclient_t *c, gchar *filename_utf8) +mpdclient_cmd_load_playlist(struct mpdclient *c, const gchar *filename_utf8) { - mpd_sendLoadCommand(c->connection, filename_utf8); - c->need_update = TRUE; + if (MPD_ERROR(c)) + return -1; + + mpd_send_load(c->connection, filename_utf8); return mpdclient_finish_command(c); } gint -mpdclient_cmd_delete_playlist(mpdclient_t *c, gchar *filename_utf8) +mpdclient_cmd_delete_playlist(struct mpdclient *c, const 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 management 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); -} + if (MPD_ERROR(c)) + return -1; -void -mpdclient_install_error_callback(mpdclient_t *c, mpdc_error_cb_t cb) -{ - c->error_callbacks = g_list_append(c->error_callbacks, cb); -} + mpd_send_rm(c->connection, filename_utf8); + if ((retval = mpdclient_finish_command(c)) == 0) + c->events |= MPD_IDLE_STORED_PLAYLIST; -void -mpdclient_remove_error_callback(mpdclient_t *c, mpdc_error_cb_t cb) -{ - c->error_callbacks = g_list_remove(c->error_callbacks, cb); + return retval; } @@ -619,62 +671,60 @@ mpdclient_remove_error_callback(mpdclient_t *c, mpdc_error_cb_t cb) /****************************************************************************/ /* update playlist */ -gint -mpdclient_playlist_update(mpdclient_t *c) +bool +mpdclient_playlist_update(struct mpdclient *c) { - mpd_InfoEntity *entity; + struct mpd_entity *entity; if (MPD_ERROR(c)) - return -1; + return false; playlist_clear(&c->playlist); - mpd_sendPlaylistInfoCommand(c->connection,-1); - while ((entity = mpd_getNextInfoEntity(c->connection))) { - if (entity->type == MPD_INFO_ENTITY_TYPE_SONG) - playlist_append(&c->playlist, entity->info.song); + mpd_send_list_queue_meta(c->connection); + while ((entity = mpd_recv_entity(c->connection))) { + if (mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) + playlist_append(&c->playlist, mpd_entity_get_song(entity)); - mpd_freeInfoEntity(entity); + mpd_entity_free(entity); } - c->playlist.id = c->status->playlist; + c->playlist.version = mpd_status_get_queue_version(c->status); c->song = NULL; - /* call playlist updated callbacks */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL); - - return mpdclient_finish_command(c); + return mpdclient_finish_command(c) == 0; } -#ifdef ENABLE_PLCHANGES - /* update playlist (plchanges) */ -gint -mpdclient_playlist_update_changes(mpdclient_t *c) +bool +mpdclient_playlist_update_changes(struct mpdclient *c) { - mpd_InfoEntity *entity; + struct mpd_song *song; + guint length; if (MPD_ERROR(c)) - return -1; + return false; - mpd_sendPlChangesCommand(c->connection, c->playlist.id); + mpd_send_queue_changes_meta(c->connection, c->playlist.version); - while ((entity = mpd_getNextInfoEntity(c->connection)) != NULL) { - struct mpd_song *song = entity->info.song; + while ((song = mpd_recv_song(c->connection)) != NULL) { + int pos = mpd_song_get_pos(song); - if (song->pos >= 0 && (guint)song->pos < c->playlist.list->len) { + if (pos >= 0 && (guint)pos < c->playlist.list->len) { /* update song */ - playlist_replace(&c->playlist, song->pos, song); + playlist_replace(&c->playlist, pos, song); } else { /* add a new song */ playlist_append(&c->playlist, song); } - mpd_freeInfoEntity(entity); + mpd_song_free(song); } /* remove trailing songs */ - while ((guint)c->status->playlistLength < c->playlist.list->len) { + + length = mpd_status_get_queue_length(c->status); + while (length < c->playlist.list->len) { guint pos = c->playlist.list->len - 1; /* Remove the last playlist entry */ @@ -682,73 +732,50 @@ mpdclient_playlist_update_changes(mpdclient_t *c) } c->song = NULL; - c->playlist.id = c->status->playlist; + c->playlist.version = mpd_status_get_queue_version(c->status); - mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL); - - return 0; + return mpdclient_finish_command(c) == 0; } -#else -gint -mpdclient_playlist_update_changes(mpdclient_t *c) -{ - return mpdclient_playlist_update(c); -} -#endif - /****************************************************************************/ /*** Filelist functions *****************************************************/ /****************************************************************************/ -mpdclient_filelist_t * -mpdclient_filelist_get(mpdclient_t *c, const gchar *path) +struct filelist * +mpdclient_filelist_get(struct mpdclient *c, const gchar *path) { - mpdclient_filelist_t *filelist; - mpd_InfoEntity *entity; - gboolean has_dirs_only = TRUE; + struct filelist *filelist; + struct mpd_entity *entity; - mpd_sendLsInfoCommand(c->connection, path); - filelist = filelist_new(path); - if (path && path[0] && strcmp(path, "/")) - /* add a dummy entry for ./.. */ - filelist_append(filelist, NULL); + if (MPD_ERROR(c)) + return NULL; - while ((entity=mpd_getNextInfoEntity(c->connection))) { + mpd_send_list_meta(c->connection, path); + filelist = filelist_new(); + + while ((entity = mpd_recv_entity(c->connection)) != NULL) filelist_append(filelist, entity); - if (has_dirs_only && entity->type != MPD_INFO_ENTITY_TYPE_DIRECTORY) { - has_dirs_only = FALSE; - } + if (mpdclient_finish_command(c)) { + filelist_free(filelist); + return NULL; } - /* If there's an error, ignore it. We'll return an empty filelist. */ - mpdclient_finish_command(c); - - // If there are only directory entities in the filelist, we sort it - if (has_dirs_only) - filelist_sort(filelist, compare_filelistentry_dir); + filelist_sort_dir_play(filelist, compare_filelistentry); return filelist; } -mpdclient_filelist_t * -mpdclient_filelist_search(mpdclient_t *c, - int exact_match, - int table, - gchar *filter_utf8) +static struct filelist * +mpdclient_recv_filelist_response(struct mpdclient *c) { - mpdclient_filelist_t *filelist; - mpd_InfoEntity *entity; + struct filelist *filelist; + struct mpd_entity *entity; - if (exact_match) - mpd_sendFindCommand(c->connection, table, filter_utf8); - else - mpd_sendSearchCommand(c->connection, table, filter_utf8); - filelist = filelist_new(NULL); + filelist = filelist_new(); - while ((entity=mpd_getNextInfoEntity(c->connection))) + while ((entity = mpd_recv_entity(c->connection)) != NULL) filelist_append(filelist, entity); if (mpdclient_finish_command(c)) { @@ -759,54 +786,72 @@ mpdclient_filelist_search(mpdclient_t *c, return filelist; } -mpdclient_filelist_t * -mpdclient_filelist_update(mpdclient_t *c, mpdclient_filelist_t *filelist) +struct filelist * +mpdclient_filelist_search(struct mpdclient *c, + int exact_match, + enum mpd_tag_type tag, + gchar *filter_utf8) { - if (filelist != NULL) { - gchar *path = g_strdup(filelist->path); + if (MPD_ERROR(c)) + return NULL; - filelist_free(filelist); - filelist = mpdclient_filelist_get(c, path); - g_free(path); - return filelist; - } - return NULL; + mpd_search_db_songs(c->connection, exact_match); + mpd_search_add_tag_constraint(c->connection, MPD_OPERATOR_DEFAULT, + tag, filter_utf8); + mpd_search_commit(c->connection); + + return mpdclient_recv_filelist_response(c); } int -mpdclient_filelist_add_all(mpdclient_t *c, mpdclient_filelist_t *fl) +mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl) { guint i; + if (MPD_ERROR(c)) + return -1; + if (filelist_is_empty(fl)) return 0; - mpd_sendCommandListBegin(c->connection); + mpd_command_list_begin(c->connection, false); for (i = 0; i < filelist_length(fl); ++i) { - filelist_entry_t *entry = filelist_get(fl, i); - mpd_InfoEntity *entity = entry->entity; + struct filelist_entry *entry = filelist_get(fl, i); + struct mpd_entity *entity = entry->entity; - if (entity && entity->type == MPD_INFO_ENTITY_TYPE_SONG) { - struct mpd_song *song = entity->info.song; + if (entity != NULL && + mpd_entity_get_type(entity) == MPD_ENTITY_TYPE_SONG) { + const struct mpd_song *song = + mpd_entity_get_song(entity); + const char *uri = mpd_song_get_uri(song); - mpd_sendAddCommand(c->connection, song->file); + if (uri != NULL) + mpd_send_add(c->connection, uri); } } - mpd_sendCommandListEnd(c->connection); + mpd_command_list_end(c->connection); return mpdclient_finish_command(c); } GList * -mpdclient_get_artists(mpdclient_t *c) +mpdclient_get_artists(struct mpdclient *c) { - gchar *str = NULL; GList *list = NULL; + struct mpd_pair *pair; + + if (MPD_ERROR(c)) + return NULL; - mpd_sendListCommand(c->connection, MPD_TABLE_ARTIST, NULL); - while ((str = mpd_getNextArtist(c->connection))) - list = g_list_append(list, (gpointer) str); + mpd_search_db_tags(c->connection, MPD_TAG_ARTIST); + mpd_search_commit(c->connection); + + while ((pair = mpd_recv_pair_tag(c->connection, + MPD_TAG_ARTIST)) != NULL) { + list = g_list_append(list, g_strdup(pair->value)); + mpd_return_pair(c->connection, pair); + } if (mpdclient_finish_command(c)) return string_list_free(list); @@ -815,14 +860,26 @@ mpdclient_get_artists(mpdclient_t *c) } GList * -mpdclient_get_albums(mpdclient_t *c, gchar *artist_utf8) +mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8) { - gchar *str = NULL; GList *list = NULL; + struct mpd_pair *pair; - mpd_sendListCommand(c->connection, MPD_TABLE_ALBUM, artist_utf8); - while ((str = mpd_getNextAlbum(c->connection))) - list = g_list_append(list, (gpointer) str); + if (MPD_ERROR(c)) + return NULL; + + mpd_search_db_tags(c->connection, MPD_TAG_ALBUM); + if (artist_utf8 != NULL) + mpd_search_add_tag_constraint(c->connection, + MPD_OPERATOR_DEFAULT, + MPD_TAG_ARTIST, artist_utf8); + mpd_search_commit(c->connection); + + while ((pair = mpd_recv_pair_tag(c->connection, + MPD_TAG_ALBUM)) != NULL) { + list = g_list_append(list, g_strdup(pair->value)); + mpd_return_pair(c->connection, pair); + } if (mpdclient_finish_command(c)) return string_list_free(list);