Code

mpdclient: added mpdclient_cmd_delete_range()
[ncmpc.git] / src / mpdclient.c
index 2d6598518086d5cd080d13aa949207a33a77a7aa..d60436946a378753538c1711cb653d0ce4bc9c16 100644 (file)
@@ -32,8 +32,6 @@
 #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 BUFSIZE 1024
@@ -118,11 +116,7 @@ mpdclient_handle_error(struct mpdclient *c)
        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);
@@ -145,6 +139,7 @@ mpdclient_new(void)
        c = g_new0(struct mpdclient, 1);
        playlist_init(&c->playlist);
        c->volume = -1;
+       c->events = 0;
 
        return c;
 }
@@ -156,9 +151,6 @@ 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);
        g_free(c);
 }
 
@@ -221,6 +213,10 @@ mpdclient_update(struct mpdclient *c)
        if (MPD_ERROR(c))
                return false;
 
+       /* always announce these options as long as we don't have real
+          "idle" support */
+       c->events |= MPD_IDLE_PLAYER|MPD_IDLE_OPTIONS;
+
        /* free the old status */
        if (c->status)
                mpd_status_free(c->status);
@@ -230,15 +226,24 @@ mpdclient_update(struct mpdclient *c)
        if (c->status == NULL)
                return mpdclient_handle_error(c) == 0;
 
-       if (c->update_id > 0 &&
-           c->update_id != mpd_status_get_update_id(c->status))
-               mpdclient_browse_callback(c, BROWSE_DB_UPDATED, NULL);
+       if (c->update_id != mpd_status_get_update_id(c->status)) {
+               c->events |= MPD_IDLE_UPDATE;
+
+               if (c->update_id > 0)
+                       c->events |= MPD_IDLE_DATABASE;
+       }
 
        c->update_id = mpd_status_get_update_id(c->status);
+
+       if (c->volume != mpd_status_get_volume(c->status))
+               c->events |= MPD_IDLE_MIXER;
+
        c->volume = mpd_status_get_volume(c->status);
 
        /* check if the playlist needs an update */
-       if (c->playlist.id != mpd_status_get_queue_version(c->status)) {
+       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
@@ -328,8 +333,10 @@ mpdclient_cmd_clear(struct mpdclient *c)
 
        mpd_send_clear(c->connection);
        retval = mpdclient_finish_command(c);
-       /* call playlist updated callback */
-       mpdclient_playlist_callback(c, PLAYLIST_EVENT_CLEAR, NULL);
+
+       if (retval)
+               c->events |= MPD_IDLE_PLAYLIST;
+
        return retval;
 }
 
@@ -391,40 +398,73 @@ mpdclient_cmd_add_path(struct mpdclient *c, const gchar *path_utf8)
 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) */
+
+       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);
 
-#ifdef ENABLE_FANCY_PLAYLIST_MANAGMENT_CMD_ADD
-       /* add the song to playlist */
-       playlist_append(&c->playlist, song);
+       c->events |= MPD_IDLE_PLAYLIST;
 
-       /* increment the playlist id, so we don't retrieve a new playlist */
-       c->playlist.id++;
+       status = mpd_recv_status(c->connection);
+       if (status != NULL) {
+               if (c->status != NULL)
+                       mpd_status_free(c->status);
+               c->status = status;
+       }
 
-       /* call playlist updated callback */
-       mpdclient_playlist_callback(c, PLAYLIST_EVENT_ADD, (gpointer) song);
-#endif
+       if (!mpd_response_next(c->connection))
+               return mpdclient_handle_error(c);
 
-       return 0;
+       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))
@@ -432,27 +472,119 @@ mpdclient_cmd_delete(struct mpdclient *c, gint idx)
 
        song = playlist_get(&c->playlist, idx);
 
-       /* 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;
+       /* 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);
 
-       /* remove the song from the playlist */
-       playlist_remove_reuse(&c->playlist, idx);
+       c->events |= MPD_IDLE_PLAYLIST;
 
-       /* call playlist updated callback */
-       mpdclient_playlist_callback(c, PLAYLIST_EVENT_DELETE, (gpointer) song);
+       status = mpd_recv_status(c->connection);
+       if (status != NULL) {
+               if (c->status != NULL)
+                       mpd_status_free(c->status);
+               c->status = status;
+       }
 
-       /* remove references to the song */
-       if (c->song == song)
-               c->song = NULL;
+       if (!mpd_response_finish(c->connection))
+               return mpdclient_handle_error(c);
 
-       mpd_song_free(song);
-#endif
+       if (mpd_status_get_queue_length(status) == playlist_length(&c->playlist) - 1 &&
+           mpd_status_get_queue_version(status) == c->playlist.version + 1) {
+               /* the cheap route: match on the new playlist length
+                  and its version, we can keep our local playlist
+                  copy in sync */
+               c->playlist.version = mpd_status_get_queue_version(status);
+
+               /* remove the song from the local playlist */
+               playlist_remove(&c->playlist, idx);
+
+               /* remove references to the song */
+               if (c->song == song)
+                       c->song = NULL;
+       }
+
+       return 0;
+}
+
+/**
+ * Fallback for mpdclient_cmd_delete_range() on MPD older than 0.16.
+ * It emulates the "delete range" command with a list of simple
+ * "delete" commands.
+ */
+static gint
+mpdclient_cmd_delete_range_fallback(struct mpdclient *c,
+                                   unsigned start, unsigned end)
+{
+       if (!mpd_command_list_begin(c->connection, false))
+               return mpdclient_handle_error(c);
+
+       for (; start < end; --end)
+               mpd_send_delete(c->connection, start);
+
+       if (!mpd_command_list_end(c->connection) ||
+           !mpd_response_finish(c->connection))
+               return mpdclient_handle_error(c);
+
+       return 0;
+}
+
+gint
+mpdclient_cmd_delete_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;
 }
@@ -484,11 +616,10 @@ mpdclient_cmd_move(struct mpdclient *c, gint old_index, gint new_index)
        playlist_swap(&c->playlist, old_index, new_index);
 
        /* increment the playlist id, so we don't retrieve a new playlist */
-       c->playlist.id++;
+       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;
 }
@@ -502,8 +633,10 @@ mpdclient_cmd_save_playlist(struct mpdclient *c, const gchar *filename_utf8)
                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;
 }
 
@@ -527,73 +660,9 @@ mpdclient_cmd_delete_playlist(struct mpdclient *c, const gchar *filename_utf8)
 
        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);
-}
+               c->events |= MPD_IDLE_STORED_PLAYLIST;
 
-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);
-}
-
-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;
 }
 
 
@@ -620,12 +689,9 @@ mpdclient_playlist_update(struct mpdclient *c)
                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;
 }
 
@@ -639,7 +705,7 @@ mpdclient_playlist_update_changes(struct mpdclient *c)
        if (MPD_ERROR(c))
                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);
@@ -666,9 +732,7 @@ 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;
 }