X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fmpdclient.c;h=ec058061546c23cf1033076d220b267745ff2949;hb=270f65da11a9c0e46af421afed54389dbc8453e5;hp=dbf285e0727bf60f941852102c308ab2984f758a;hpb=36470979d9f3518217fc26987558dafb3ab36f5c;p=ncmpc.git diff --git a/src/mpdclient.c b/src/mpdclient.c index dbf285e..ec05806 100644 --- a/src/mpdclient.c +++ b/src/mpdclient.c @@ -1,5 +1,6 @@ -/* - * (c) 2004 by Kalle Wallin +/* ncmpc (Ncurses MPD Client) + * (c) 2004-2010 The Music Player Daemon Project + * Project homepage: http://musicpd.org * * 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 @@ -10,81 +11,47 @@ * 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 * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "mpdclient.h" -#include "screen_utils.h" +#include "filelist.h" +#include "screen_client.h" #include "config.h" -#include "ncmpc.h" -#include "support.h" #include "options.h" #include "strfsong.h" +#include "utils.h" +#include "gidle.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); - - -/* filelist sorting functions */ -static gint -compare_filelistentry_dir(gconstpointer filelist_entry1, - gconstpointer filelist_entry2) -{ - const mpd_InfoEntity *e1, *e2; - char *key1, *key2; - 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) { - key1 = g_utf8_collate_key(e1->info.directory->path,-1); - key2 = g_utf8_collate_key(e2->info.directory->path,-1); - n = strcmp(key1,key2); - g_free(key1); - g_free(key2); - } - - return n; -} - /* sort by list-format */ 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, LIST_FORMAT, e1->info.song); - strfsong(key2, BUFSIZE, 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); } @@ -92,90 +59,68 @@ 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) +bool +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; - gchar *msg; + 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 true; - if (error == MPD_ERROR_ACK) - error = error | (c->connection->errorCode << 8); + mpdclient_ui_error(mpd_connection_get_error_message(c->connection)); - msg = locale_to_utf8(c->connection->errorStr); - error_cb(c, error, msg); - g_free(msg); - return error; - } + if (!mpd_connection_clear_error(c->connection)) + mpdclient_disconnect(c); - return 0; + return false; } -mpdclient_t * +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->source != NULL) { + mpd_glib_free(c->source); + c->source = NULL; + c->idle = false; + } + 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); @@ -183,532 +128,590 @@ mpdclient_disconnect(mpdclient_t *c) if (c->song) c->song = NULL; - return 0; + /* everything has changed after a disconnect */ + c->events |= MPD_IDLE_ALL; } -gint -mpdclient_connect(mpdclient_t *c, - gchar *host, +bool +mpdclient_connect(struct mpdclient *c, + const gchar *host, gint port, - gfloat _timeout, - gchar *password) + unsigned timeout_ms, + const gchar *password) { - gint retval = 0; - /* close any open connection */ - if( c->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_ms); + 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; + struct mpd_connection *connection = mpdclient_get_connection(c); - if (MPD_ERROR(c)) - return -1; + c->volume = -1; + + if (connection == NULL) + return false; + + /* always announce these options as long as we don't have + "idle" support */ + if (c->source == NULL) + 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 */ + c->status = mpd_run_status(connection); + if (c->status == NULL) + return mpdclient_handle_error(c); + + if (c->source == NULL && + 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->source == NULL && + c->volume != mpd_status_get_volume(c->status)) + c->events |= MPD_IDLE_MIXER; - /* retreive new status */ - mpd_sendStatusCommand(c->connection); - c->status = mpd_getStatus(c->connection); - if ((retval=mpdclient_finish_command(c))) - return retval; + 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)) { + bool retval; + + if (c->source == NULL) + c->events |= MPD_IDLE_QUEUE; + + if (!playlist_is_empty(&c->playlist)) retval = mpdclient_playlist_update_changes(c); else retval = mpdclient_playlist_update(c); + if (!retval) + return false; } /* 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) >= 0) { + c->song = playlist_get_song(&c->playlist, + mpd_status_get_song_pos(c->status)); } - c->need_update = FALSE; - - return retval; + return true; } +struct mpd_connection * +mpdclient_get_connection(struct mpdclient *c) +{ + if (c->source != NULL && c->idle) { + c->idle = false; + mpd_glib_leave(c->source); + } -/****************************************************************************/ -/*** MPD Commands **********************************************************/ -/****************************************************************************/ + return c->connection; +} -gint -mpdclient_cmd_play(mpdclient_t *c, gint idx) +void +mpdclient_put_connection(struct mpdclient *c) { -#ifdef ENABLE_SONG_ID - struct mpd_song *song = playlist_get_song(c, idx); - - if (song) - mpd_sendPlayIdCommand(c->connection, song->id); - 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); + assert(c->source == NULL || c->connection != NULL); + + if (c->source != NULL && !c->idle) { + c->idle = mpd_glib_enter(c->source); + } } -gint -mpdclient_cmd_pause(mpdclient_t *c, gint value) +static struct mpd_status * +mpdclient_recv_status(struct mpdclient *c) { - mpd_sendPauseCommand(c->connection, value); - return mpdclient_finish_command(c); + struct mpd_status *status; + + assert(c->connection != NULL); + + status = mpd_recv_status(c->connection); + if (status == NULL) { + mpdclient_handle_error(c); + return NULL; + } + + if (c->status != NULL) + mpd_status_free(c->status); + return c->status = status; } -gint -mpdclient_cmd_crop(mpdclient_t *c) +/****************************************************************************/ +/*** MPD Commands **********************************************************/ +/****************************************************************************/ + +bool +mpdclient_cmd_crop(struct mpdclient *c) { - mpd_Status *status; - int length; + struct mpd_connection *connection; + int length, current; - mpd_sendStatusCommand(c->connection); - status = mpd_getStatus(c->connection); - length = status->playlistLength - 1; + if (!mpdclient_is_playing(c)) + return false; - if (length <= 0) { - mpd_freeStatus(status); - } else if (status->state == 3 || status->state == 2) { - /* If playing or paused */ + length = mpd_status_get_queue_length(c->status); + current = mpd_status_get_song_pos(c->status); + if (current < 0 || mpd_status_get_queue_length(c->status) < 2) + return true; - mpd_sendCommandListBegin( c->connection ); + connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - while (length >= 0) { - if (length != status->song) - mpd_sendDeleteCommand(c->connection, length); + mpd_command_list_begin(connection, false); - length--; - } + if (mpd_connection_cmp_server_version(connection, 0, 16, 0) >= 0) { + if (current < length - 1) + mpd_send_delete_range(connection, current + 1, length); + if (current > 0) + mpd_send_delete_range(connection, 0, current); + } else + while (--length >= 0) + if (length != current) + mpd_send_delete(connection, length); - mpd_sendCommandListEnd(c->connection); - mpd_freeStatus(status); - } else { - mpd_freeStatus(status); - } + mpd_command_list_end(connection); return mpdclient_finish_command(c); } -gint -mpdclient_cmd_stop(mpdclient_t *c) +bool +mpdclient_cmd_clear(struct mpdclient *c) { - mpd_sendStopCommand(c->connection); - return mpdclient_finish_command(c); -} + struct mpd_connection *connection = mpdclient_get_connection(c); + struct mpd_status *status; -gint -mpdclient_cmd_next(mpdclient_t *c) -{ - mpd_sendNextCommand(c->connection); - c->need_update = TRUE; - return mpdclient_finish_command(c); -} + if (connection == NULL) + return false; -gint -mpdclient_cmd_prev(mpdclient_t *c) -{ - mpd_sendPrevCommand(c->connection); - c->need_update = TRUE; - return mpdclient_finish_command(c); -} + /* send "clear" and "status" */ + if (!mpd_command_list_begin(connection, false) || + !mpd_send_clear(connection) || + !mpd_send_status(connection) || + !mpd_command_list_end(connection)) + return mpdclient_handle_error(c); -gint -mpdclient_cmd_seek(mpdclient_t *c, gint id, gint pos) -{ - mpd_sendSeekIdCommand(c->connection, id, pos); - return mpdclient_finish_command(c); -} + /* receive the new status, store it in the mpdclient struct */ -gint -mpdclient_cmd_shuffle(mpdclient_t *c) -{ - mpd_sendShuffleCommand(c->connection); - c->need_update = TRUE; - return mpdclient_finish_command(c); -} + status = mpdclient_recv_status(c); + if (status == NULL) + return false; -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; -} + if (!mpd_response_finish(connection)) + return mpdclient_handle_error(c); -gint -mpdclient_cmd_repeat(mpdclient_t *c, gint value) -{ - mpd_sendRepeatCommand(c->connection, value); - return mpdclient_finish_command(c); -} + /* update mpdclient.playlist */ -gint -mpdclient_cmd_random(mpdclient_t *c, gint value) -{ - mpd_sendRandomCommand(c->connection, value); - return mpdclient_finish_command(c); -} + if (mpd_status_get_queue_length(status) == 0) { + /* after the "clear" command, the queue is really + empty - this means we can clear it locally, + reducing the UI latency */ + playlist_clear(&c->playlist); + c->playlist.version = mpd_status_get_queue_version(status); + c->song = NULL; + } -gint -mpdclient_cmd_crossfade(mpdclient_t *c, gint value) -{ - mpd_sendCrossfadeCommand(c->connection, value); - return mpdclient_finish_command(c); + c->events |= MPD_IDLE_QUEUE; + return true; } -gint -mpdclient_cmd_db_update_utf8(mpdclient_t *c, gchar *path) +bool +mpdclient_cmd_volume(struct mpdclient *c, gint value) { - mpd_sendUpdateCommand(c->connection, path ? path : ""); - return mpdclient_finish_command(c); -} + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; -gint -mpdclient_cmd_volume(mpdclient_t *c, gint value) -{ - mpd_sendSetvolCommand(c->connection, value); + mpd_send_set_volume(connection, value); return mpdclient_finish_command(c); } -gint -mpdclient_cmd_add_path_utf8(mpdclient_t *c, gchar *path_utf8) +bool +mpdclient_cmd_volume_up(struct mpdclient *c) { - mpd_sendAddCommand(c->connection, path_utf8); - return mpdclient_finish_command(c); -} + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; -gint -mpdclient_cmd_add_path(mpdclient_t *c, gchar *path) -{ - gint retval; - gchar *path_utf8 = locale_to_utf8(path); + if (c->status == NULL || + mpd_status_get_volume(c->status) == -1) + return true; - retval=mpdclient_cmd_add_path_utf8(c, path_utf8); - g_free(path_utf8); - return retval; + if (c->volume < 0) + c->volume = mpd_status_get_volume(c->status); + + if (c->volume >= 100) + return true; + + return mpdclient_cmd_volume(c, ++c->volume); } -gint -mpdclient_cmd_add(mpdclient_t *c, struct mpd_song *song) +bool +mpdclient_cmd_volume_down(struct mpdclient *c) { - gint retval = 0; + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - if( !song || !song->file ) - return -1; + if (c->status == NULL || mpd_status_get_volume(c->status) < 0) + return true; - /* send the add command to mpd */ - mpd_sendAddCommand(c->connection, song->file); - if( (retval=mpdclient_finish_command(c)) ) - return retval; + if (c->volume < 0) + c->volume = mpd_status_get_volume(c->status); -#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD - /* add the song to playlist */ - playlist_append(&c->playlist, song); + if (c->volume <= 0) + return true; - /* increment the playlist id, so we dont retrives a new playlist */ - c->playlist.id++; + return mpdclient_cmd_volume(c, --c->volume); +} - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song); -#else - c->need_update = TRUE; -#endif +bool +mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8) +{ + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - return 0; + return mpd_send_add(connection, path_utf8)? + mpdclient_finish_command(c) : false; } -gint -mpdclient_cmd_delete(mpdclient_t *c, gint idx) +bool +mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song) { - gint retval = 0; - struct mpd_song *song; + struct mpd_connection *connection = mpdclient_get_connection(c); + struct mpd_status *status; + struct mpd_song *new_song; - if (idx < 0 || (guint)idx >= playlist_length(&c->playlist)) - return -1; + assert(c != NULL); + assert(song != NULL); - song = playlist_get(&c->playlist, idx); + if (connection == NULL || c->status == NULL) + return false; - /* 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; + /* 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) */ -#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE - /* increment the playlist id, so we dont retrive a new playlist */ - c->playlist.id++; + if (!mpd_command_list_begin(connection, true) || + !mpd_send_add(connection, mpd_song_get_uri(song)) || + !mpd_send_status(connection) || + !mpd_send_get_queue_song_pos(connection, + playlist_length(&c->playlist)) || + !mpd_command_list_end(connection) || + !mpd_response_next(connection)) + return mpdclient_handle_error(c); - /* remove the song from the playlist */ - playlist_remove_reuse(&c->playlist, idx); + c->events |= MPD_IDLE_QUEUE; - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song); + status = mpdclient_recv_status(c); + if (status == NULL) + return false; - /* remove references to the song */ - if (c->song == song) { - c->song = NULL; - c->need_update = TRUE; + if (!mpd_response_next(connection)) + return mpdclient_handle_error(c); + + new_song = mpd_recv_song(connection); + if (!mpd_response_finish(connection) || new_song == NULL) { + if (new_song != NULL) + mpd_song_free(new_song); + + return mpd_connection_clear_error(connection) || + mpdclient_handle_error(c); } - mpd_freeSong(song); + 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); + } -#else - c->need_update = TRUE; -#endif + mpd_song_free(new_song); - return 0; + return true; } -gint -mpdclient_cmd_move(mpdclient_t *c, gint old_index, gint new_index) +bool +mpdclient_cmd_delete(struct mpdclient *c, gint idx) { - gint n; - struct mpd_song *song1, *song2; + struct mpd_connection *connection = mpdclient_get_connection(c); + const struct mpd_song *song; + struct mpd_status *status; - if (old_index == new_index || new_index < 0 || - (guint)new_index >= c->playlist.list->len) - return -1; + if (connection == NULL || c->status == NULL) + return false; - song1 = playlist_get(&c->playlist, old_index); - song2 = playlist_get(&c->playlist, new_index); + if (idx < 0 || (guint)idx >= playlist_length(&c->playlist)) + return false; - /* 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 - if( (n=mpdclient_finish_command(c)) ) - return n; + song = playlist_get(&c->playlist, idx); -#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE - /* update the playlist */ - playlist_swap(&c->playlist, old_index, new_index); + /* send the delete command to mpd; at the same time, get the + new status (to verify the playlist id) */ - /* increment the playlist id, so we dont retrives a new playlist */ - c->playlist.id++; + if (!mpd_command_list_begin(connection, false) || + !mpd_send_delete_id(connection, mpd_song_get_id(song)) || + !mpd_send_status(connection) || + !mpd_command_list_end(connection)) + return mpdclient_handle_error(c); -#else - c->need_update = TRUE; -#endif + c->events |= MPD_IDLE_QUEUE; - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index); + status = mpdclient_recv_status(c); + if (status == NULL) + return false; - return 0; -} + if (!mpd_response_finish(connection)) + return mpdclient_handle_error(c); -gint -mpdclient_cmd_save_playlist_utf8(mpdclient_t *c, gchar *filename_utf8) -{ - gint retval = 0; + 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); - mpd_sendSaveCommand(c->connection, filename_utf8); - if ((retval = mpdclient_finish_command(c)) == 0) - mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL); - return retval; -} + /* remove the song from the local playlist */ + playlist_remove(&c->playlist, idx); -gint -mpdclient_cmd_save_playlist(mpdclient_t *c, gchar *filename) -{ - gint retval = 0; - gchar *filename_utf8 = locale_to_utf8(filename); + /* remove references to the song */ + if (c->song == song) + c->song = NULL; + } - retval = mpdclient_cmd_save_playlist_utf8(c, filename); - g_free(filename_utf8); - return retval; + return true; } -gint -mpdclient_cmd_load_playlist_utf8(mpdclient_t *c, gchar *filename_utf8) +/** + * 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 bool +mpdclient_cmd_delete_range_fallback(struct mpdclient *c, + unsigned start, unsigned end) { - mpd_sendLoadCommand(c->connection, filename_utf8); - c->need_update = TRUE; - return mpdclient_finish_command(c); -} + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; -gint -mpdclient_cmd_delete_playlist_utf8(mpdclient_t *c, gchar *filename_utf8) -{ - gint retval = 0; + if (!mpd_command_list_begin(connection, false)) + return mpdclient_handle_error(c); + + for (; start < end; --end) + mpd_send_delete(connection, start); - mpd_sendRmCommand(c->connection, filename_utf8); - if ((retval = mpdclient_finish_command(c)) == 0) - mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL); - return retval; + if (!mpd_command_list_end(connection) || + !mpd_response_finish(connection)) + return mpdclient_handle_error(c); + + return true; } -gint -mpdclient_cmd_delete_playlist(mpdclient_t *c, gchar *filename) +bool +mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end) { - gint retval = 0; - gchar *filename_utf8 = locale_to_utf8(filename); + struct mpd_connection *connection; + struct mpd_status *status; - retval = mpdclient_cmd_delete_playlist_utf8(c, filename_utf8); - g_free(filename_utf8); - return retval; -} + if (end == start + 1) + /* if that's not really a range, we choose to use the + safer "deleteid" version */ + return mpdclient_cmd_delete(c, start); + connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; -/****************************************************************************/ -/*** Callback managment functions *******************************************/ -/****************************************************************************/ + if (mpd_connection_cmp_server_version(connection, 0, 16, 0) < 0) + return mpdclient_cmd_delete_range_fallback(c, start, end); -static void -do_list_callbacks(mpdclient_t *c, GList *list, gint event, gpointer data) -{ - while (list) { - mpdc_list_cb_t fn = list->data; + /* MPD 0.16 supports "delete" with a range argument */ + + /* send the delete command to mpd; at the same time, get the + new status (to verify the playlist id) */ + + if (!mpd_command_list_begin(connection, false) || + !mpd_send_delete_range(connection, start, end) || + !mpd_send_status(connection) || + !mpd_command_list_end(connection)) + return mpdclient_handle_error(c); + + c->events |= MPD_IDLE_QUEUE; + + status = mpdclient_recv_status(c); + if (status == NULL) + return false; + + if (!mpd_response_finish(connection)) + return mpdclient_handle_error(c); - fn(c, event, data); - list = list->next; + 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); + } } -} -void -mpdclient_playlist_callback(mpdclient_t *c, int event, gpointer data) -{ - do_list_callbacks(c, c->playlist_callbacks, event, data); + return true; } -void -mpdclient_install_playlist_callback(mpdclient_t *c,mpdc_list_cb_t cb) +bool +mpdclient_cmd_move(struct mpdclient *c, unsigned dest_pos, unsigned src_pos) { - c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb); -} + struct mpd_connection *connection; + struct mpd_status *status; -void -mpdclient_remove_playlist_callback(mpdclient_t *c, mpdc_list_cb_t cb) -{ - c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb); -} + if (dest_pos == src_pos) + return true; -void -mpdclient_browse_callback(mpdclient_t *c, int event, gpointer data) -{ - do_list_callbacks(c, c->browse_callbacks, event, data); -} + connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; + /* send the "move" command to MPD; at the same time, get the + new status (to verify the playlist id) */ -void -mpdclient_install_browse_callback(mpdclient_t *c,mpdc_list_cb_t cb) -{ - c->browse_callbacks = g_list_append(c->browse_callbacks, cb); -} + if (!mpd_command_list_begin(connection, false) || + !mpd_send_move(connection, src_pos, dest_pos) || + !mpd_send_status(connection) || + !mpd_command_list_end(connection)) + return mpdclient_handle_error(c); -void -mpdclient_remove_browse_callback(mpdclient_t *c, mpdc_list_cb_t cb) -{ - c->browse_callbacks = g_list_remove(c->browse_callbacks, cb); -} + c->events |= MPD_IDLE_QUEUE; -void -mpdclient_install_error_callback(mpdclient_t *c, mpdc_error_cb_t cb) -{ - c->error_callbacks = g_list_append(c->error_callbacks, cb); -} + status = mpdclient_recv_status(c); + if (status == NULL) + return false; -void -mpdclient_remove_error_callback(mpdclient_t *c, mpdc_error_cb_t cb) -{ - c->error_callbacks = g_list_remove(c->error_callbacks, cb); + if (!mpd_response_finish(connection)) + return mpdclient_handle_error(c); + + if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) && + 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); + + /* swap songs in the local playlist */ + playlist_move(&c->playlist, dest_pos, src_pos); + } + + return true; } /****************************************************************************/ -/*** Playlist managment functions *******************************************/ +/*** Playlist management functions ******************************************/ /****************************************************************************/ /* update playlist */ -gint -mpdclient_playlist_update(mpdclient_t *c) +bool +mpdclient_playlist_update(struct mpdclient *c) { - mpd_InfoEntity *entity; + struct mpd_connection *connection = mpdclient_get_connection(c); + struct mpd_entity *entity; - if (MPD_ERROR(c)) - return -1; + if (connection == NULL) + 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(connection); + while ((entity = mpd_recv_entity(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); } -#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_connection *connection = mpdclient_get_connection(c); + struct mpd_song *song; + guint length; - if (MPD_ERROR(c)) - return -1; + if (connection == NULL) + return false; - mpd_sendPlChangesCommand(c->connection, c->playlist.id); + mpd_send_queue_changes_meta(connection, c->playlist.version); - while ((entity = mpd_getNextInfoEntity(c->connection)) != NULL) { - struct mpd_song *song = entity->info.song; + while ((song = mpd_recv_song(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 */ @@ -716,170 +719,43 @@ mpdclient_playlist_update_changes(mpdclient_t *c) } c->song = NULL; - c->playlist.id = c->status->playlist; - - mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL); - - return 0; -} + c->playlist.version = mpd_status_get_queue_version(c->status); -#else -gint -mpdclient_playlist_update_changes(mpdclient_t *c) -{ - return mpdclient_playlist_update(c); + return mpdclient_finish_command(c); } -#endif /****************************************************************************/ /*** Filelist functions *****************************************************/ /****************************************************************************/ -mpdclient_filelist_t * -mpdclient_filelist_get(mpdclient_t *c, const gchar *path) -{ - mpdclient_filelist_t *filelist; - mpd_InfoEntity *entity; - gchar *path_utf8 = locale_to_utf8(path); - gboolean has_dirs_only = TRUE; - - mpd_sendLsInfoCommand(c->connection, path_utf8); - filelist = filelist_new(path); - if (path && path[0] && strcmp(path, "/")) - /* add a dummy entry for ./.. */ - filelist_append(filelist, NULL); - - while ((entity=mpd_getNextInfoEntity(c->connection))) { - filelist_append(filelist, entity); - - if (has_dirs_only && entity->type != MPD_INFO_ENTITY_TYPE_DIRECTORY) { - has_dirs_only = FALSE; - } - } - - /* If there's an error, ignore it. We'll return an empty filelist. */ - mpdclient_finish_command(c); - - g_free(path_utf8); - - // If there are only directory entities in the filelist, we sort it - if (has_dirs_only) - filelist_sort(filelist, compare_filelistentry_dir); - - return filelist; -} - -mpdclient_filelist_t * -mpdclient_filelist_search_utf8(mpdclient_t *c, - int exact_match, - int table, - gchar *filter_utf8) -{ - mpdclient_filelist_t *filelist; - mpd_InfoEntity *entity; - - if (exact_match) - mpd_sendFindCommand(c->connection, table, filter_utf8); - else - mpd_sendSearchCommand(c->connection, table, filter_utf8); - filelist = filelist_new(NULL); - - while ((entity=mpd_getNextInfoEntity(c->connection))) - filelist_append(filelist, entity); - - if (mpdclient_finish_command(c)) { - filelist_free(filelist); - return NULL; - } - - return filelist; -} - - -mpdclient_filelist_t * -mpdclient_filelist_search(mpdclient_t *c, - int exact_match, - int table, - gchar *_filter) -{ - mpdclient_filelist_t *filelist; - gchar *filter_utf8 = locale_to_utf8(_filter); - - filelist = mpdclient_filelist_search_utf8(c, exact_match, table, - filter_utf8); - g_free(filter_utf8); - - 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_free(filelist); - filelist = mpdclient_filelist_get(c, path); - g_free(path); - return filelist; - } - return NULL; -} - -int -mpdclient_filelist_add_all(mpdclient_t *c, mpdclient_filelist_t *fl) +bool +mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl) { + struct mpd_connection *connection = mpdclient_get_connection(c); guint i; + if (connection == NULL) + return false; + if (filelist_is_empty(fl)) - return 0; + return true; - mpd_sendCommandListBegin(c->connection); + mpd_command_list_begin(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); - mpd_sendAddCommand(c->connection, song->file); + mpd_send_add(connection, mpd_song_get_uri(song)); } } - mpd_sendCommandListEnd(c->connection); + mpd_command_list_end(connection); return mpdclient_finish_command(c); } - -GList * -mpdclient_get_artists_utf8(mpdclient_t *c) -{ - gchar *str = NULL; - GList *list = NULL; - - mpd_sendListCommand(c->connection, MPD_TABLE_ARTIST, NULL); - while ((str = mpd_getNextArtist(c->connection))) - list = g_list_append(list, (gpointer) str); - - if (mpdclient_finish_command(c)) - return string_list_free(list); - - return list; -} - -GList * -mpdclient_get_albums_utf8(mpdclient_t *c, gchar *artist_utf8) -{ - gchar *str = NULL; - GList *list = NULL; - - mpd_sendListCommand(c->connection, MPD_TABLE_ALBUM, artist_utf8); - while ((str = mpd_getNextAlbum(c->connection))) - list = g_list_append(list, (gpointer) str); - - if (mpdclient_finish_command(c)) - return string_list_free(list); - - return list; -}