diff --git a/src/mpdclient.c b/src/mpdclient.c
index a688162a1832640f0938051d4b1a08afe90e6176..d60436946a378753538c1711cb653d0ce4bc9c16 100644 (file)
--- a/src/mpdclient.c
+++ b/src/mpdclient.c
#include "mpdclient.h"
#include "filelist.h"
-#include "screen_utils.h"
+#include "screen_client.h"
#include "config.h"
#include "options.h"
#include "strfsong.h"
#include <time.h>
#include <string.h>
-#undef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD /* broken with song id's */
-#define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
#define ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_MOVE
-#define ENABLE_SONG_ID
-#define ENABLE_PLCHANGES
#define BUFSIZE 1024
/*** mpdclient functions ****************************************************/
/****************************************************************************/
-static gint
+gint
mpdclient_handle_error(struct mpdclient *c)
{
enum mpd_error error = mpd_connection_get_error(c->connection);
if (error == MPD_ERROR_SERVER &&
mpd_connection_get_server_error(c->connection) == MPD_SERVER_ERROR_PERMISSION &&
- screen_auth(c) == 0)
+ screen_auth(c))
return 0;
if (error == MPD_ERROR_SERVER)
error = error | (mpd_connection_get_server_error(c->connection) << 8);
- 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));
- }
+ mpdclient_ui_error(mpd_connection_get_error_message(c->connection));
if (!mpd_connection_clear_error(c->connection))
mpdclient_disconnect(c);
return error;
}
-gint
+static gint
mpdclient_finish_command(struct mpdclient *c)
{
return mpd_response_finish(c->connection)
c = g_new0(struct mpdclient, 1);
playlist_init(&c->playlist);
- c->volume = MPD_STATUS_NO_VOLUME;
+ c->volume = -1;
+ c->events = 0;
return 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
+void
mpdclient_disconnect(struct mpdclient *c)
{
if (c->connection)
if (c->song)
c->song = NULL;
-
- return 0;
}
-gint
+bool
mpdclient_connect(struct mpdclient *c,
const gchar *host,
gint port,
gfloat _timeout,
const gchar *password)
{
- gint retval = 0;
-
/* close any open connection */
if( c->connection )
mpdclient_disconnect(c);
g_error("Out of memory");
if (mpd_connection_get_error(c->connection) != MPD_ERROR_SUCCESS) {
- retval = mpdclient_handle_error(c);
- if (retval != 0) {
- mpd_connection_free(c->connection);
- c->connection = NULL;
- }
-
- return retval;
+ mpdclient_handle_error(c);
+ mpdclient_disconnect(c);
+ return false;
}
/* send password */
- if( password ) {
- mpd_send_password(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
+bool
mpdclient_update(struct mpdclient *c)
{
- gint retval = 0;
+ bool retval;
- c->volume = MPD_STATUS_NO_VOLUME;
+ 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)
/* retrieve new status */
c->status = mpd_run_status(c->connection);
if (c->status == NULL)
- return mpdclient_handle_error(c);
+ 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;
+ }
- if (c->updatingdb &&
- c->updatingdb != mpd_status_get_update_id(c->status))
- mpdclient_browse_callback(c, BROWSE_DB_UPDATED, NULL);
+ 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->updatingdb = mpd_status_get_update_id(c->status);
c->volume = mpd_status_get_volume(c->status);
/* check if the playlist needs an update */
- if (c->playlist.id != mpd_status_get_queue_version(c->status)) {
- 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 || mpd_status_get_song_id(c->status)) {
- c->song = playlist_get_song(c, mpd_status_get_song_pos(c->status));
+ c->song = playlist_get_song(&c->playlist,
+ mpd_status_get_song_pos(c->status));
}
- c->need_update = FALSE;
-
return retval;
}
gint
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;
mpd_send_play_id(c->connection, mpd_song_get_id(song));
else
mpd_send_play(c->connection);
-#else
- if (MPD_ERROR(c))
- return -1;
-
- mpd_sendPlayCommand(c->connection, idx);
-#endif
- c->need_update = TRUE;
- return mpdclient_finish_command(c);
-}
-gint
-mpdclient_cmd_pause(struct mpdclient *c, gint value)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_pause(c->connection, value);
return mpdclient_finish_command(c);
}
return mpdclient_finish_command(c);
}
-gint
-mpdclient_cmd_stop(struct mpdclient *c)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_stop(c->connection);
- return mpdclient_finish_command(c);
-}
-
-gint
-mpdclient_cmd_next(struct mpdclient *c)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_next(c->connection);
- c->need_update = TRUE;
- return mpdclient_finish_command(c);
-}
-
-gint
-mpdclient_cmd_prev(struct mpdclient *c)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_previous(c->connection);
- c->need_update = TRUE;
- return mpdclient_finish_command(c);
-}
-
-gint
-mpdclient_cmd_seek(struct mpdclient *c, gint id, gint pos)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_seek_id(c->connection, id, pos);
- return mpdclient_finish_command(c);
-}
-
-gint
-mpdclient_cmd_shuffle(struct mpdclient *c)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_shuffle(c->connection);
- c->need_update = TRUE;
- return mpdclient_finish_command(c);
-}
-
gint
mpdclient_cmd_shuffle_range(struct mpdclient *c, guint start, guint end)
{
mpd_send_shuffle_range(c->connection, start, end);
- c->need_update = TRUE;
return mpdclient_finish_command(c);
}
mpd_send_clear(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(struct mpdclient *c, gint value)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_repeat(c->connection, value);
- return mpdclient_finish_command(c);
-}
-
-gint
-mpdclient_cmd_random(struct mpdclient *c, gint value)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_random(c->connection, value);
- return mpdclient_finish_command(c);
-}
-
-gint
-mpdclient_cmd_single(struct mpdclient *c, gint value)
-{
- if (MPD_ERROR(c))
- return -1;
- mpd_send_single(c->connection, value);
- return mpdclient_finish_command(c);
-}
+ if (retval)
+ c->events |= MPD_IDLE_PLAYLIST;
-gint
-mpdclient_cmd_consume(struct mpdclient *c, gint value)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_consume(c->connection, value);
- return mpdclient_finish_command(c);
-}
-
-gint
-mpdclient_cmd_crossfade(struct mpdclient *c, gint value)
-{
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_crossfade(c->connection, value);
- return mpdclient_finish_command(c);
-}
-
-gint
-mpdclient_cmd_db_update(struct mpdclient *c, const gchar *path)
-{
- gint ret;
-
- if (MPD_ERROR(c))
- return -1;
-
- mpd_send_update(c->connection, path ? path : "");
- ret = mpdclient_finish_command(c);
-
- if (ret == 0)
- /* set updatingDb to make sure the browse callback
- gets called even if the update has finished before
- status is updated */
- c->updatingdb = 1;
-
- return ret;
+ return retval;
}
gint
return -1;
if (c->status == NULL ||
- mpd_status_get_volume(c->status) == MPD_STATUS_NO_VOLUME)
+ mpd_status_get_volume(c->status) == -1)
return 0;
- if (c->volume == MPD_STATUS_NO_VOLUME)
+ if (c->volume < 0)
c->volume = mpd_status_get_volume(c->status);
if (c->volume >= 100)
if (MPD_ERROR(c))
return -1;
- if (c->status == NULL ||
- mpd_status_get_volume(c->status) == MPD_STATUS_NO_VOLUME)
+ if (c->status == NULL || mpd_status_get_volume(c->status) < 0)
return 0;
- if (c->volume == MPD_STATUS_NO_VOLUME)
+ if (c->volume < 0)
c->volume = mpd_status_get_volume(c->status);
if (c->volume <= 0)
gint
mpdclient_cmd_add(struct mpdclient *c, const struct mpd_song *song)
{
- gint retval = 0;
+ struct mpd_status *status;
+ struct mpd_song *new_song;
- if (MPD_ERROR(c))
- return -1;
+ assert(c != NULL);
+ assert(song != NULL);
- if (song == NULL)
+ if (MPD_ERROR(c) || c->status == NULL)
return -1;
- /* send the add command to mpd */
- mpd_send_add(c->connection, mpd_song_get_uri(song));
- 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_ADD
- /* add the song to playlist */
- playlist_append(&c->playlist, song);
+ 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);
- /* increment the playlist id, so we don't retrieve a new playlist */
- c->playlist.id++;
+ c->events |= MPD_IDLE_PLAYLIST;
- /* call playlist updated callback */
- mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
-#else
- c->need_update = TRUE;
-#endif
+ status = mpd_recv_status(c->connection);
+ if (status != NULL) {
+ if (c->status != NULL)
+ mpd_status_free(c->status);
+ c->status = status;
+ }
- return 0;
+ 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_delete(struct mpdclient *c, gint idx)
{
- gint retval = 0;
- struct mpd_song *song;
+ const struct mpd_song *song;
+ struct mpd_status *status;
- if (MPD_ERROR(c))
+ if (MPD_ERROR(c) || c->status == NULL)
return -1;
if (idx < 0 || (guint)idx >= playlist_length(&c->playlist))
song = playlist_get(&c->playlist, idx);
- /* send the delete command to mpd */
-#ifdef ENABLE_SONG_ID
- mpd_send_delete_id(c->connection, mpd_song_get_id(song));
-#else
- mpd_send_delete(c->connection, idx);
-#endif
- if( (retval=mpdclient_finish_command(c)) )
- return retval;
+ /* send the delete command to mpd; at the same time, get the
+ new status (to verify the playlist id) */
-#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_DELETE
- /* increment the playlist id, so we don't retrieve a new playlist */
- c->playlist.id++;
+ 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;
- /* remove the song from the playlist */
- playlist_remove_reuse(&c->playlist, idx);
+ 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);
- /* call playlist updated callback */
- mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) 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);
- /* remove references to the song */
- if (c->song == song) {
- c->song = NULL;
- c->need_update = TRUE;
+ /* remove the song from the local playlist */
+ playlist_remove(&c->playlist, idx);
+
+ /* remove references to the song */
+ if (c->song == song)
+ c->song = NULL;
}
- mpd_song_free(song);
+ return 0;
+}
-#else
- c->need_update = TRUE;
-#endif
+/**
+ * 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_range(struct mpdclient *c, unsigned start, unsigned end)
+{
+ struct mpd_status *status;
+
+ if (MPD_ERROR(c))
+ return -1;
+
+ if (mpd_connection_cmp_server_version(c->connection, 0, 16, 0) < 0)
+ return mpdclient_cmd_delete_range_fallback(c, start, end);
+
+ /* 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(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);
+
+ 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) - (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;
}
song2 = playlist_get(&c->playlist, new_index);
/* send the move command to mpd */
-#ifdef ENABLE_SONG_ID
mpd_send_swap_id(c->connection,
mpd_song_get_id(song1), mpd_song_get_id(song2));
-#else
- mpd_send_move(c->connection, old_index, new_index);
-#endif
if( (n=mpdclient_finish_command(c)) )
return n;
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;
}
return -1;
mpd_send_save(c->connection, filename_utf8);
- if ((retval = mpdclient_finish_command(c)) == 0)
- mpdclient_browse_callback(c, BROWSE_PLAYLIST_SAVED, NULL);
+ if ((retval = mpdclient_finish_command(c)) == 0) {
+ c->events |= MPD_IDLE_STORED_PLAYLIST;
+ }
+
return retval;
}
return -1;
mpd_send_load(c->connection, filename_utf8);
- c->need_update = TRUE;
return mpdclient_finish_command(c);
}
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 ******************************************/
-/****************************************************************************/
-
-static void
-do_list_callbacks(struct mpdclient *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(struct mpdclient *c, int event, gpointer data)
-{
- do_list_callbacks(c, c->playlist_callbacks, event, data);
-}
-
-void
-mpdclient_install_playlist_callback(struct mpdclient *c,mpdc_list_cb_t cb)
-{
- c->playlist_callbacks = g_list_append(c->playlist_callbacks, cb);
-}
-
-void
-mpdclient_remove_playlist_callback(struct mpdclient *c, mpdc_list_cb_t cb)
-{
- c->playlist_callbacks = g_list_remove(c->playlist_callbacks, cb);
-}
-
-void
-mpdclient_browse_callback(struct mpdclient *c, int event, gpointer data)
-{
- do_list_callbacks(c, c->browse_callbacks, event, data);
-}
-
-
-void
-mpdclient_install_browse_callback(struct mpdclient *c,mpdc_list_cb_t cb)
-{
- c->browse_callbacks = g_list_append(c->browse_callbacks, cb);
-}
+ c->events |= MPD_IDLE_STORED_PLAYLIST;
-void
-mpdclient_remove_browse_callback(struct mpdclient *c, mpdc_list_cb_t cb)
-{
- c->browse_callbacks = g_list_remove(c->browse_callbacks, cb);
-}
-
-void
-mpdclient_install_error_callback(struct mpdclient *c, mpdc_error_cb_t cb)
-{
- c->error_callbacks = g_list_append(c->error_callbacks, cb);
-}
-
-void
-mpdclient_remove_error_callback(struct mpdclient *c, mpdc_error_cb_t cb)
-{
- c->error_callbacks = g_list_remove(c->error_callbacks, cb);
+ return retval;
}
/****************************************************************************/
/* update playlist */
-gint
+bool
mpdclient_playlist_update(struct mpdclient *c)
{
struct mpd_entity *entity;
if (MPD_ERROR(c))
- return -1;
+ return false;
playlist_clear(&c->playlist);
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);
+ return mpdclient_finish_command(c) == 0;
}
-#ifdef ENABLE_PLCHANGES
-
/* update playlist (plchanges) */
-gint
+bool
mpdclient_playlist_update_changes(struct mpdclient *c)
{
struct mpd_song *song;
guint length;
if (MPD_ERROR(c))
- return -1;
+ return false;
- mpd_send_queue_changes_meta(c->connection, c->playlist.id);
+ mpd_send_queue_changes_meta(c->connection, c->playlist.version);
while ((song = mpd_recv_song(c->connection)) != NULL) {
int pos = mpd_song_get_pos(song);
}
c->song = NULL;
- c->playlist.id = mpd_status_get_queue_version(c->status);
-
- 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(struct mpdclient *c)
-{
- return mpdclient_playlist_update(c);
+ return mpdclient_finish_command(c) == 0;
}
-#endif
/****************************************************************************/
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);
+ if (mpdclient_finish_command(c)) {
+ filelist_free(filelist);
+ return NULL;
+ }
filelist_sort_dir_play(filelist, compare_filelistentry);