X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fmpdclient.c;h=ffa1a5d84408e86757b82d2dbde1617dbf07c583;hb=efae3a330fbfe9b85690b0b01a5b640e95613a6e;hp=77638d65c7b5691d4190200989dafc8d5bdcf5eb;hpb=4f552ec230a1a4241b664e84be0c654f3b563651;p=ncmpc.git diff --git a/src/mpdclient.c b/src/mpdclient.c index 77638d6..ffa1a5d 100644 --- a/src/mpdclient.c +++ b/src/mpdclient.c @@ -1,109 +1,136 @@ /* ncmpc (Ncurses MPD Client) - * (c) 2004-2009 The Music Player Daemon Project + * (c) 2004-2017 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 * 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., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. -*/ + */ #include "mpdclient.h" +#include "callbacks.h" #include "filelist.h" -#include "screen_client.h" #include "config.h" -#include "options.h" -#include "strfsong.h" -#include "utils.h" +#include "gidle.h" +#include "charset.h" + +#ifdef ENABLE_ASYNC_CONNECT +#include "aconnect.h" +#endif #include -#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 +static gboolean +mpdclient_enter_idle_callback(gpointer user_data) +{ + struct mpdclient *c = user_data; + assert(c->enter_idle_source_id != 0); + assert(c->source != NULL); + assert(!c->idle); -#define BUFSIZE 1024 + c->enter_idle_source_id = 0; + c->idle = mpd_glib_enter(c->source); + return false; +} -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(gconstpointer filelist_entry1, - gconstpointer filelist_entry2) -{ - const struct mpd_entity *e1, *e2; - int n = 0; - - 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))); - } +static void +mpdclient_schedule_enter_idle(struct mpdclient *c) +{ + assert(c != NULL); + assert(c->source != NULL); + + if (c->enter_idle_source_id == 0) + /* automatically re-enter MPD "idle" mode */ + c->enter_idle_source_id = + g_idle_add(mpdclient_enter_idle_callback, c); +} + +static void +mpdclient_cancel_enter_idle(struct mpdclient *c) +{ + if (c->enter_idle_source_id != 0) { + g_source_remove(c->enter_idle_source_id); + c->enter_idle_source_id = 0; } - return n; } -/* sort by list-format */ -gint -compare_filelistentry_format(gconstpointer filelist_entry1, - gconstpointer filelist_entry2) +static void +mpdclient_invoke_error_callback(enum mpd_error error, + const char *message) +{ + char *allocated; + if (error == MPD_ERROR_SERVER) + /* server errors are UTF-8, the others are locale */ + message = allocated = utf8_to_locale(message); + else + allocated = NULL; + + mpdclient_error_callback(message); + g_free(allocated); +} + +static void +mpdclient_invoke_error_callback1(struct mpdclient *c) { - const struct mpd_entity *e1, *e2; - char key1[BUFSIZE], key2[BUFSIZE]; - int n = 0; + assert(c != NULL); + assert(c->connection != NULL); - e1 = ((const struct filelist_entry *)filelist_entry1)->entity; - e2 = ((const struct filelist_entry *)filelist_entry2)->entity; + struct mpd_connection *connection = c->connection; - if (e1 && e2 && - 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); - } + enum mpd_error error = mpd_connection_get_error(connection); + assert(error != MPD_ERROR_SUCCESS); - return n; + mpdclient_invoke_error_callback(error, + mpd_connection_get_error_message(connection)); } +static void +mpdclient_gidle_callback(enum mpd_error error, + gcc_unused enum mpd_server_error server_error, + const char *message, enum mpd_idle events, + void *ctx) +{ + struct mpdclient *c = ctx; + + c->idle = false; + + assert(mpdclient_is_connected(c)); + + if (error != MPD_ERROR_SUCCESS) { + mpdclient_invoke_error_callback(error, message); + mpdclient_disconnect(c); + mpdclient_lost_callback(); + return; + } + + c->events |= events; + mpdclient_update(c); + + mpdclient_idle_callback(c->events); + + c->events = 0; + + if (c->source != NULL) + mpdclient_schedule_enter_idle(c); +} /****************************************************************************/ /*** mpdclient functions ****************************************************/ /****************************************************************************/ -gint +bool mpdclient_handle_error(struct mpdclient *c) { enum mpd_error error = mpd_connection_get_error(c->connection); @@ -112,39 +139,70 @@ mpdclient_handle_error(struct mpdclient *c) if (error == MPD_ERROR_SERVER && mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION && - screen_auth(c)) - return 0; + mpdclient_auth_callback(c)) + return true; - if (error == MPD_ERROR_SERVER) - error = error | (mpd_connection_get_server_error(c->connection) << 8); + mpdclient_invoke_error_callback(error, + mpd_connection_get_error_message(c->connection)); - for (GList *list = c->error_callbacks; list != NULL; - list = list->next) { - mpdc_error_cb_t cb = list->data; - cb(c, error, mpd_connection_get_error_message(c->connection)); + if (!mpd_connection_clear_error(c->connection)) { + mpdclient_disconnect(c); + mpdclient_lost_callback(); } - if (!mpd_connection_clear_error(c->connection)) - mpdclient_disconnect(c); + return false; +} + +#ifdef ENABLE_ASYNC_CONNECT +#ifndef WIN32 - return error; +static bool +is_local_socket(const char *host) +{ + return *host == '/' || *host == '@'; } -static gint -mpdclient_finish_command(struct mpdclient *c) +static bool +settings_is_local_socket(const struct mpd_settings *settings) { - return mpd_response_finish(c->connection) - ? 0 : mpdclient_handle_error(c); + const char *host = mpd_settings_get_host(settings); + return host != NULL && is_local_socket(host); } +#endif +#endif + struct mpdclient * -mpdclient_new(void) +mpdclient_new(const gchar *host, unsigned port, + unsigned timeout_ms, const gchar *password) { - struct mpdclient *c; + struct mpdclient *c = g_new0(struct mpdclient, 1); + +#ifdef ENABLE_ASYNC_CONNECT + c->settings = mpd_settings_new(host, port, timeout_ms, + NULL, NULL); + if (c->settings == NULL) + g_error("Out of memory"); + +#ifndef WIN32 + c->settings2 = host == NULL && port == 0 && + settings_is_local_socket(c->settings) + ? mpd_settings_new(host, 6600, timeout_ms, NULL, NULL) + : NULL; +#endif + +#else + c->host = host; + c->port = port; +#endif + + c->timeout_ms = timeout_ms; + c->password = password; - c = g_new0(struct mpdclient, 1); playlist_init(&c->playlist); - c->volume = MPD_STATUS_NO_VOLUME; + c->volume = -1; + c->events = 0; + c->playing = false; return c; } @@ -156,557 +214,671 @@ mpdclient_free(struct mpdclient *c) mpdclient_playlist_free(&c->playlist); - g_list_free(c->error_callbacks); - g_list_free(c->playlist_callbacks); - g_list_free(c->browse_callbacks); +#ifdef ENABLE_ASYNC_CONNECT + mpd_settings_free(c->settings); + +#ifndef WIN32 + if (c->settings2 != NULL) + mpd_settings_free(c->settings2); +#endif +#endif + g_free(c); } +static char * +settings_name(const struct mpd_settings *settings) +{ + assert(settings != NULL); + + const char *host = mpd_settings_get_host(settings); + if (host == NULL) + host = "unknown"; + + if (host[0] == '/') + return g_strdup(host); + + unsigned port = mpd_settings_get_port(settings); + if (port == 0 || port == 6600) + return g_strdup(host); + + return g_strdup_printf("%s:%u", host, port); +} + +char * +mpdclient_settings_name(const struct mpdclient *c) +{ + assert(c != NULL); + +#ifdef ENABLE_ASYNC_CONNECT + return settings_name(c->settings); +#else + struct mpd_settings *settings = + mpd_settings_new(c->host, c->port, 0, NULL, NULL); + if (settings == NULL) + return g_strdup("unknown"); + + char *name = settings_name(settings); + mpd_settings_free(settings); + return name; +#endif +} + +static void +mpdclient_status_free(struct mpdclient *c) +{ + if (c->status == NULL) + return; + + mpd_status_free(c->status); + c->status = NULL; + + c->volume = -1; + c->playing = false; +} + void mpdclient_disconnect(struct mpdclient *c) { - if (c->connection) +#ifdef ENABLE_ASYNC_CONNECT + if (c->async_connect != NULL) { + aconnect_cancel(c->async_connect); + c->async_connect = NULL; + } +#endif + + mpdclient_cancel_enter_idle(c); + + if (c->source != NULL) { + mpd_glib_free(c->source); + c->source = NULL; + c->idle = false; + } + + if (c->connection) { mpd_connection_free(c->connection); + ++c->connection_id; + } c->connection = NULL; - if (c->status) - mpd_status_free(c->status); - c->status = NULL; + mpdclient_status_free(c); playlist_clear(&c->playlist); if (c->song) c->song = NULL; + + /* everything has changed after a disconnect */ + c->events |= MPD_IDLE_ALL; } -bool -mpdclient_connect(struct mpdclient *c, - const gchar *host, - gint port, - gfloat _timeout, - const gchar *password) +static bool +mpdclient_connected(struct mpdclient *c, + struct mpd_connection *connection) { - /* close any open connection */ - if( c->connection ) - mpdclient_disconnect(c); - - /* connect to MPD */ - c->connection = mpd_connection_new(host, port, _timeout * 1000); - if (c->connection == NULL) - g_error("Out of memory"); + c->connection = connection; - if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) { - mpdclient_handle_error(c); + if (mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS) { + mpdclient_invoke_error_callback1(c); mpdclient_disconnect(c); + mpdclient_failed_callback(); return false; } +#ifdef ENABLE_ASYNC_CONNECT + if (c->timeout_ms > 0) + mpd_connection_set_timeout(connection, c->timeout_ms); +#endif + /* send password */ - if (password != NULL && !mpd_run_password(c->connection, password)) { - mpdclient_handle_error(c); + if (c->password != NULL && + !mpd_run_password(connection, c->password)) { + mpdclient_invoke_error_callback1(c); mpdclient_disconnect(c); + mpdclient_failed_callback(); return false; } + c->source = mpd_glib_new(connection, + mpdclient_gidle_callback, c); + mpdclient_schedule_enter_idle(c); + + ++c->connection_id; + + mpdclient_connected_callback(); return true; } +#ifdef ENABLE_ASYNC_CONNECT + +static void +mpdclient_aconnect_start(struct mpdclient *c, + const struct mpd_settings *settings); + +static void +mpdclient_connect_success(struct mpd_connection *connection, void *ctx) +{ + struct mpdclient *c = ctx; + assert(c->async_connect != NULL); + c->async_connect = NULL; + + mpdclient_connected(c, connection); +} + +static void +mpdclient_connect_error(const char *message, void *ctx) +{ + struct mpdclient *c = ctx; + assert(c->async_connect != NULL); + c->async_connect = NULL; + +#ifndef WIN32 + if (!c->connecting2 && c->settings2 != NULL) { + c->connecting2 = true; + mpdclient_aconnect_start(c, c->settings2); + return; + } +#endif + + mpdclient_error_callback(message); + mpdclient_failed_callback(); +} + +static const struct aconnect_handler mpdclient_connect_handler = { + .success = mpdclient_connect_success, + .error = mpdclient_connect_error, +}; + +static void +mpdclient_aconnect_start(struct mpdclient *c, + const struct mpd_settings *settings) +{ + aconnect_start(&c->async_connect, + mpd_settings_get_host(settings), + mpd_settings_get_port(settings), + &mpdclient_connect_handler, c); +} + +#endif + +void +mpdclient_connect(struct mpdclient *c) +{ + /* close any open connection */ + mpdclient_disconnect(c); + +#ifdef ENABLE_ASYNC_CONNECT +#ifndef WIN32 + c->connecting2 = false; +#endif + mpdclient_aconnect_start(c, c->settings); +#else + /* connect to MPD */ + struct mpd_connection *connection = + mpd_connection_new(c->host, c->port, c->timeout_ms); + if (connection == NULL) + g_error("Out of memory"); + + mpdclient_connected(c, connection); +#endif +} + bool mpdclient_update(struct mpdclient *c) { - bool retval; - - c->volume = MPD_STATUS_NO_VOLUME; + struct mpd_connection *connection = mpdclient_get_connection(c); - if (MPD_ERROR(c)) + if (connection == NULL) return false; /* free the old status */ - if (c->status) - mpd_status_free(c->status); + mpdclient_status_free(c); /* retrieve new status */ - c->status = mpd_run_status(c->connection); + c->status = mpd_run_status(connection); if (c->status == NULL) - return mpdclient_handle_error(c) == 0; - - if (c->updatingdb && - c->updatingdb != mpd_status_get_update_id(c->status)) - mpdclient_browse_callback(c, BROWSE_DB_UPDATED, NULL); + return mpdclient_handle_error(c); - c->updatingdb = mpd_status_get_update_id(c->status); c->volume = mpd_status_get_volume(c->status); + c->playing = mpd_status_get_state(c->status) == MPD_STATE_PLAY; /* check if the playlist needs an update */ - if (c->playlist.id != mpd_status_get_queue_version(c->status)) { + if (c->playlist.version != mpd_status_get_queue_version(c->status)) { + bool retval; + 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 || mpd_status_get_song_id(c->status)) { + 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)); } - return retval; + return true; } - -/****************************************************************************/ -/*** MPD Commands **********************************************************/ -/****************************************************************************/ - -gint -mpdclient_cmd_play(struct mpdclient *c, gint idx) +struct mpd_connection * +mpdclient_get_connection(struct mpdclient *c) { - const struct mpd_song *song = playlist_get_song(&c->playlist, idx); - - if (MPD_ERROR(c)) - return -1; + if (c->source != NULL && c->idle) { + c->idle = false; + mpd_glib_leave(c->source); - if (song) - mpd_send_play_id(c->connection, mpd_song_get_id(song)); - else - mpd_send_play(c->connection); + mpdclient_schedule_enter_idle(c); + } - return mpdclient_finish_command(c); + return c->connection; } -gint -mpdclient_cmd_pause(struct mpdclient *c, gint value) +static struct mpd_status * +mpdclient_recv_status(struct mpdclient *c) { - if (MPD_ERROR(c)) - return -1; + assert(c->connection != NULL); - mpd_send_pause(c->connection, value); - return mpdclient_finish_command(c); + struct mpd_status *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 +/****************************************************************************/ +/*** MPD Commands **********************************************************/ +/****************************************************************************/ + +bool mpdclient_cmd_crop(struct mpdclient *c) { - struct mpd_status *status; - bool playing; - int length, current; - - if (MPD_ERROR(c)) - return -1; - - status = mpd_run_status(c->connection); - if (status == NULL) - return mpdclient_handle_error(c); - - 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); + if (!mpdclient_is_playing(c)) + return false; - mpd_status_free(status); + int length = mpd_status_get_queue_length(c->status); + int current = mpd_status_get_song_pos(c->status); + if (current < 0 || mpd_status_get_queue_length(c->status) < 2) + return true; - if (!playing || length < 2) - return 0; + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - mpd_command_list_begin(c->connection, false); + mpd_command_list_begin(connection, false); - while (--length >= 0) - if (length != current) - mpd_send_delete(c->connection, length); + if (current < length - 1) + mpd_send_delete_range(connection, current + 1, length); + if (current > 0) + mpd_send_delete_range(connection, 0, current); - mpd_command_list_end(c->connection); + mpd_command_list_end(connection); return mpdclient_finish_command(c); } -gint -mpdclient_cmd_stop(struct mpdclient *c) +bool +mpdclient_cmd_clear(struct mpdclient *c) { - if (MPD_ERROR(c)) - return -1; + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - mpd_send_stop(c->connection); - 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_next(struct mpdclient *c) -{ - if (MPD_ERROR(c)) - return -1; + /* receive the new status, store it in the mpdclient struct */ - mpd_send_next(c->connection); - return mpdclient_finish_command(c); -} + struct mpd_status *status = mpdclient_recv_status(c); + if (status == NULL) + return false; -gint -mpdclient_cmd_prev(struct mpdclient *c) -{ - if (MPD_ERROR(c)) - return -1; + if (!mpd_response_finish(connection)) + return mpdclient_handle_error(c); - mpd_send_previous(c->connection); - return mpdclient_finish_command(c); -} + /* update mpdclient.playlist */ -gint -mpdclient_cmd_seek(struct mpdclient *c, gint id, gint pos) -{ - if (MPD_ERROR(c)) - return -1; + 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; + } - mpd_send_seek_id(c->connection, id, pos); - return mpdclient_finish_command(c); + c->events |= MPD_IDLE_QUEUE; + return true; } -gint -mpdclient_cmd_shuffle(struct mpdclient *c) +bool +mpdclient_cmd_volume(struct mpdclient *c, gint value) { - if (MPD_ERROR(c)) - return -1; - - mpd_send_shuffle(c->connection); - return mpdclient_finish_command(c); -} + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; -gint -mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end) -{ - mpd_send_shuffle_range(c->connection, start, end); + mpd_send_set_volume(connection, value); return mpdclient_finish_command(c); } -gint -mpdclient_cmd_clear(struct mpdclient *c) +bool +mpdclient_cmd_volume_up(struct mpdclient *c) { - gint retval = 0; + if (c->volume < 0 || c->volume >= 100) + return true; - if (MPD_ERROR(c)) - return -1; + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - mpd_send_clear(c->connection); - retval = mpdclient_finish_command(c); - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL); - return retval; + return mpdclient_cmd_volume(c, ++c->volume); } -gint -mpdclient_cmd_repeat(struct mpdclient *c, gint value) +bool +mpdclient_cmd_volume_down(struct mpdclient *c) { - if (MPD_ERROR(c)) - return -1; - - mpd_send_repeat(c->connection, value); - return mpdclient_finish_command(c); -} + if (c->volume <= 0) + return true; -gint -mpdclient_cmd_random(struct mpdclient *c, gint value) -{ - if (MPD_ERROR(c)) - return -1; + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - mpd_send_random(c->connection, value); - return mpdclient_finish_command(c); + return mpdclient_cmd_volume(c, --c->volume); } -gint -mpdclient_cmd_single(struct mpdclient *c, gint value) +bool +mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8) { - if (MPD_ERROR(c)) - return -1; + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - mpd_send_single(c->connection, value); - return mpdclient_finish_command(c); + return mpd_send_add(connection, path_utf8)? + mpdclient_finish_command(c) : false; } -gint -mpdclient_cmd_consume(struct mpdclient *c, gint value) +bool +mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song) { - if (MPD_ERROR(c)) - return -1; + assert(c != NULL); + assert(song != NULL); - mpd_send_consume(c->connection, value); - return mpdclient_finish_command(c); -} + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL || c->status == NULL) + return false; -gint -mpdclient_cmd_crossfade(struct mpdclient *c, gint value) -{ - if (MPD_ERROR(c)) - 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(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); - mpd_send_crossfade(c->connection, value); - return mpdclient_finish_command(c); -} + c->events |= MPD_IDLE_QUEUE; -gint -mpdclient_cmd_volume(struct mpdclient *c, gint value) -{ - if (MPD_ERROR(c)) - return -1; + struct mpd_status *status = mpdclient_recv_status(c); + if (status == NULL) + return false; - mpd_send_set_volume(c->connection, value); - return mpdclient_finish_command(c); -} + if (!mpd_response_next(connection)) + return mpdclient_handle_error(c); -gint mpdclient_cmd_volume_up(struct mpdclient *c) -{ - if (MPD_ERROR(c)) - return -1; + struct mpd_song *new_song = mpd_recv_song(connection); + if (!mpd_response_finish(connection) || new_song == NULL) { + if (new_song != NULL) + mpd_song_free(new_song); - if (c->status == NULL || - mpd_status_get_volume(c->status) == MPD_STATUS_NO_VOLUME) - return 0; + return mpd_connection_clear_error(connection) || + mpdclient_handle_error(c); + } - if (c->volume == MPD_STATUS_NO_VOLUME) - c->volume = mpd_status_get_volume(c->status); + 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); - if (c->volume >= 100) - return 0; + /* the song we just received has the correct id; + append it to the local playlist */ + playlist_append(&c->playlist, new_song); + } - return mpdclient_cmd_volume(c, ++c->volume); + mpd_song_free(new_song); + + return true; } -gint mpdclient_cmd_volume_down(struct mpdclient *c) +bool +mpdclient_cmd_delete(struct mpdclient *c, gint idx) { - if (MPD_ERROR(c)) - return -1; + struct mpd_connection *connection = mpdclient_get_connection(c); - if (c->status == NULL || - mpd_status_get_volume(c->status) == MPD_STATUS_NO_VOLUME) - return 0; - - if (c->volume == MPD_STATUS_NO_VOLUME) - c->volume = mpd_status_get_volume(c->status); - - if (c->volume <= 0) - return 0; + if (connection == NULL || c->status == NULL) + return false; - return mpdclient_cmd_volume(c, --c->volume); -} + if (idx < 0 || (guint)idx >= playlist_length(&c->playlist)) + return false; -gint -mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8) -{ - if (MPD_ERROR(c)) - return -1; + const struct mpd_song *song = playlist_get(&c->playlist, idx); - mpd_send_add(c->connection, path_utf8); - return mpdclient_finish_command(c); -} + /* send the delete command to mpd; at the same time, get the + new status (to verify the playlist id) */ -gint -mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song) -{ - gint retval = 0; + 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); - if (MPD_ERROR(c)) - return -1; + c->events |= MPD_IDLE_QUEUE; - if (song == NULL) - return -1; + struct mpd_status *status = mpdclient_recv_status(c); + if (status == NULL) + return false; - /* send the add command to mpd */ - mpd_send_add(c->connection, mpd_song_get_uri(song)); - if( (retval=mpdclient_finish_command(c)) ) - return retval; + if (!mpd_response_finish(connection)) + return mpdclient_handle_error(c); -#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD - /* add the song to playlist */ - playlist_append(&c->playlist, 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); - /* increment the playlist id, so we don't retrieve a new playlist */ - c->playlist.id++; + /* remove the song from the local playlist */ + playlist_remove(&c->playlist, idx); - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song); -#endif + /* remove references to the song */ + if (c->song == song) + c->song = NULL; + } - return 0; + return true; } -gint -mpdclient_cmd_delete(struct mpdclient *c, gint idx) +bool +mpdclient_cmd_delete_range(struct mpdclient *c, unsigned start, unsigned end) { - gint retval = 0; - struct mpd_song *song; + 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); - if (MPD_ERROR(c)) - return -1; + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - if (idx < 0 || (guint)idx >= playlist_length(&c->playlist)) - return -1; + /* send the delete command to mpd; at the same time, get the + new status (to verify the playlist id) */ - song = playlist_get(&c->playlist, idx); + 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); - /* send the delete command to mpd */ - mpd_send_delete_id(c->connection, mpd_song_get_id(song)); - if( (retval=mpdclient_finish_command(c)) ) - return retval; + c->events |= MPD_IDLE_QUEUE; -#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE - /* increment the playlist id, so we don't retrieve a new playlist */ - c->playlist.id++; + struct mpd_status *status = mpdclient_recv_status(c); + if (status == NULL) + return false; - /* remove the song from the playlist */ - playlist_remove_reuse(&c->playlist, idx); + if (!mpd_response_finish(connection)) + return mpdclient_handle_error(c); - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song); + 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 references to the song */ - if (c->song == song) - c->song = NULL; + /* remove the song from the local playlist */ + while (end > start) { + --end; - mpd_song_free(song); -#endif + /* remove references to the song */ + if (c->song == playlist_get(&c->playlist, end)) + c->song = NULL; + + playlist_remove(&c->playlist, end); + } + } - return 0; + return true; } -gint -mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index) +bool +mpdclient_cmd_move(struct mpdclient *c, unsigned dest_pos, unsigned src_pos) { - gint n; - struct mpd_song *song1, *song2; + if (dest_pos == src_pos) + return true; - if (MPD_ERROR(c)) - return -1; + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; - if (old_index == new_index || new_index < 0 || - (guint)new_index >= c->playlist.list->len) - return -1; + /* send the "move" command to MPD; at the same time, get the + new status (to verify the playlist id) */ - song1 = playlist_get(&c->playlist, old_index); - song2 = playlist_get(&c->playlist, new_index); + 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); - /* send the move command to mpd */ - mpd_send_swap_id(c->connection, - mpd_song_get_id(song1), mpd_song_get_id(song2)); - if( (n=mpdclient_finish_command(c)) ) - return n; + c->events |= MPD_IDLE_QUEUE; -#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE - /* update the playlist */ - playlist_swap(&c->playlist, old_index, new_index); + struct mpd_status *status = mpdclient_recv_status(c); + if (status == NULL) + return false; - /* increment the playlist id, so we don't retrieve a new playlist */ - c->playlist.id++; -#endif + if (!mpd_response_finish(connection)) + return mpdclient_handle_error(c); - /* call playlist updated callback */ - mpdclient_playlist_callback(c, PLAYLIST_EVENT_MOVE, (gpointer) &new_index); + 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); - return 0; + /* swap songs in the local playlist */ + playlist_move(&c->playlist, dest_pos, src_pos); + } + + return true; } -gint -mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8) -{ - gint retval = 0; +/* The client-to-client protocol (MPD 0.17.0) */ - if (MPD_ERROR(c)) - return -1; +bool +mpdclient_cmd_subscribe(struct mpdclient *c, const char *channel) +{ + struct mpd_connection *connection = mpdclient_get_connection(c); - mpd_send_save(c->connection, filename_utf8); - if ((retval = mpdclient_finish_command(c)) == 0) - mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL); - return retval; -} + if (connection == NULL) + return false; -gint -mpdclient_cmd_load_playlist(struct mpdclient *c, const gchar *filename_utf8) -{ - if (MPD_ERROR(c)) - return -1; + if (!mpd_send_subscribe(connection, channel)) + return mpdclient_handle_error(c); - mpd_send_load(c->connection, filename_utf8); return mpdclient_finish_command(c); } -gint -mpdclient_cmd_delete_playlist(struct mpdclient *c, const gchar *filename_utf8) +bool +mpdclient_cmd_unsubscribe(struct mpdclient *c, const char *channel) { - gint retval = 0; - - if (MPD_ERROR(c)) - return -1; - - mpd_send_rm(c->connection, filename_utf8); - if ((retval = mpdclient_finish_command(c)) == 0) - mpdclient_browse_callback(c, BROWSE_PLAYLIST_DELETED, NULL); - return retval; -} - - -/****************************************************************************/ -/*** Callback management functions ******************************************/ -/****************************************************************************/ + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; -static void -do_list_callbacks(struct mpdclient *c, GList *list, gint event, gpointer data) -{ - while (list) { - mpdc_list_cb_t fn = list->data; + if (!mpd_send_unsubscribe(connection, channel)) + return mpdclient_handle_error(c); - fn(c, event, data); - list = list->next; - } + return mpdclient_finish_command(c); } -void -mpdclient_playlist_callback(struct mpdclient *c, int event, gpointer data) +bool +mpdclient_cmd_send_message(struct mpdclient *c, const char *channel, + const char *text) { - do_list_callbacks(c, c->playlist_callbacks, event, data); -} + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; -void -mpdclient_install_playlist_callback(struct mpdclient *c,mpdc_list_cb_t cb) -{ - c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb); -} + if (!mpd_send_send_message(connection, channel, text)) + return mpdclient_handle_error(c); -void -mpdclient_remove_playlist_callback(struct mpdclient *c, mpdc_list_cb_t cb) -{ - c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb); + return mpdclient_finish_command(c); } -void -mpdclient_browse_callback(struct mpdclient *c, int event, gpointer data) +bool +mpdclient_send_read_messages(struct mpdclient *c) { - do_list_callbacks(c, c->browse_callbacks, event, data); -} - + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; -void -mpdclient_install_browse_callback(struct mpdclient *c,mpdc_list_cb_t cb) -{ - c->browse_callbacks = g_list_append(c->browse_callbacks, cb); + return mpd_send_read_messages(connection)? + true : mpdclient_handle_error(c); } -void -mpdclient_remove_browse_callback(struct mpdclient *c, mpdc_list_cb_t cb) +struct mpd_message * +mpdclient_recv_message(struct mpdclient *c) { - c->browse_callbacks = g_list_remove(c->browse_callbacks, cb); -} + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; -void -mpdclient_install_error_callback(struct mpdclient *c, mpdc_error_cb_t cb) -{ - c->error_callbacks = g_list_append(c->error_callbacks, cb); -} + struct mpd_message *message = mpd_recv_message(connection); + if (message == NULL && + mpd_connection_get_error(connection) != MPD_ERROR_SUCCESS) + mpdclient_handle_error(c); -void -mpdclient_remove_error_callback(struct mpdclient *c, mpdc_error_cb_t cb) -{ - c->error_callbacks = g_list_remove(c->error_callbacks, cb); + return message; } - /****************************************************************************/ /*** Playlist management functions ******************************************/ /****************************************************************************/ @@ -715,43 +887,41 @@ mpdclient_remove_error_callback(struct mpdclient *c, mpdc_error_cb_t cb) bool mpdclient_playlist_update(struct mpdclient *c) { - struct mpd_entity *entity; - - if (MPD_ERROR(c)) + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) return false; playlist_clear(&c->playlist); - mpd_send_list_queue_meta(c->connection); - while ((entity = mpd_recv_entity(c->connection))) { + mpd_send_list_queue_meta(connection); + + struct mpd_entity *entity; + 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_entity_free(entity); } - c->playlist.id = mpd_status_get_queue_version(c->status); + 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) == 0; + return mpdclient_finish_command(c); } /* update playlist (plchanges) */ bool mpdclient_playlist_update_changes(struct mpdclient *c) { - struct mpd_song *song; - guint length; + struct mpd_connection *connection = mpdclient_get_connection(c); - if (MPD_ERROR(c)) + if (connection == NULL) return false; - mpd_send_queue_changes_meta(c->connection, c->playlist.id); + mpd_send_queue_changes_meta(connection, c->playlist.version); - while ((song = mpd_recv_song(c->connection)) != NULL) { + struct mpd_song *song; + while ((song = mpd_recv_song(connection)) != NULL) { int pos = mpd_song_get_pos(song); if (pos >= 0 && (guint)pos < c->playlist.list->len) { @@ -767,7 +937,7 @@ mpdclient_playlist_update_changes(struct mpdclient *c) /* remove trailing songs */ - length = mpd_status_get_queue_length(c->status); + unsigned length = mpd_status_get_queue_length(c->status); while (length < c->playlist.list->len) { guint pos = c->playlist.list->len - 1; @@ -776,11 +946,9 @@ mpdclient_playlist_update_changes(struct mpdclient *c) } c->song = NULL; - c->playlist.id = mpd_status_get_queue_version(c->status); - - mpdclient_playlist_callback(c, PLAYLIST_EVENT_UPDATED, NULL); + c->playlist.version = mpd_status_get_queue_version(c->status); - return mpdclient_finish_command(c) == 0; + return mpdclient_finish_command(c); } @@ -788,82 +956,19 @@ mpdclient_playlist_update_changes(struct mpdclient *c) /*** Filelist functions *****************************************************/ /****************************************************************************/ -struct filelist * -mpdclient_filelist_get(struct mpdclient *c, const gchar *path) -{ - struct filelist *filelist; - struct mpd_entity *entity; - - if (MPD_ERROR(c)) - return NULL; - - mpd_send_list_meta(c->connection, path); - filelist = filelist_new(); - if (path && path[0] && strcmp(path, "/")) - /* add a dummy entry for ./.. */ - filelist_append(filelist, NULL); - - while ((entity = mpd_recv_entity(c->connection)) != NULL) - filelist_append(filelist, entity); - - /* If there's an error, ignore it. We'll return an empty filelist. */ - mpdclient_finish_command(c); - - filelist_sort_dir_play(filelist, compare_filelistentry); - - return filelist; -} - -static struct filelist * -mpdclient_recv_filelist_response(struct mpdclient *c) -{ - struct filelist *filelist; - struct mpd_entity *entity; - - filelist = filelist_new(); - - while ((entity = mpd_recv_entity(c->connection)) != NULL) - filelist_append(filelist, entity); - - if (mpdclient_finish_command(c)) { - filelist_free(filelist); - return NULL; - } - - return filelist; -} - -struct filelist * -mpdclient_filelist_search(struct mpdclient *c, - int exact_match, - enum mpd_tag_type tag, - gchar *filter_utf8) -{ - if (MPD_ERROR(c)) - 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 +bool mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl) { - guint i; - - if (MPD_ERROR(c)) - return -1; + struct mpd_connection *connection = mpdclient_get_connection(c); + if (connection == NULL) + return false; if (filelist_is_empty(fl)) - return 0; + return true; - mpd_command_list_begin(c->connection, false); + mpd_command_list_begin(connection, false); - for (i = 0; i < filelist_length(fl); ++i) { + for (unsigned i = 0; i < filelist_length(fl); ++i) { struct filelist_entry *entry = filelist_get(fl, i); struct mpd_entity *entity = entry->entity; @@ -871,65 +976,11 @@ mpdclient_filelist_add_all(struct mpdclient *c, struct filelist *fl) 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); - if (uri != NULL) - mpd_send_add(c->connection, uri); + mpd_send_add(connection, mpd_song_get_uri(song)); } } - mpd_command_list_end(c->connection); + mpd_command_list_end(connection); return mpdclient_finish_command(c); } - -GList * -mpdclient_get_artists(struct mpdclient *c) -{ - GList *list = NULL; - struct mpd_pair *pair; - - if (MPD_ERROR(c)) - return NULL; - - 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); - - return list; -} - -GList * -mpdclient_get_albums(struct mpdclient *c, const gchar *artist_utf8) -{ - GList *list = NULL; - struct mpd_pair *pair; - - 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); - - return list; -}