[vlc-commits] core: playlist: expose "request" functions
Romain Vimont
git at videolan.org
Thu Nov 15 17:29:08 CET 2018
vlc | branch: master | Romain Vimont <rom1v at videolabs.io> | Tue Oct 16 15:16:14 2018 +0200| [b93133070a8df9bb9669c0831413d76d20e41762] | committer: Thomas Guillem
core: playlist: expose "request" functions
When a user requests to insert, move or remove items, or to set the
current item, before the core playlist lock is successfully acquired,
another client may have changed the list.
Expose functions to apply the requested changes with potential conflicts
resolved. The actual changes applied are notified through the callbacks.
Signed-off-by: Thomas Guillem <thomas at gllm.fr>
> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=b93133070a8df9bb9669c0831413d76d20e41762
---
include/vlc_playlist.h | 105 +++++++++
src/Makefile.am | 4 +-
src/libvlccore.sym | 4 +
src/playlist/request.c | 265 ++++++++++++++++++++++
src/playlist/test.c | 598 +++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 975 insertions(+), 1 deletion(-)
diff --git a/include/vlc_playlist.h b/include/vlc_playlist.h
index 6b5c6cddef..3659c2e2b4 100644
--- a/include/vlc_playlist.h
+++ b/include/vlc_playlist.h
@@ -96,6 +96,12 @@ extern "C" {
* manages a cursor to the "current" item, and expose whether a previous and
* next items (which depend on the playback order and repeat mode) are
* available.
+ *
+ * When a user requests to insert, move or remove items, or to set the current
+ * item, before the core playlist lock is successfully acquired, another client
+ * may have changed the list. Therefore, vlc_playlist_Request*() functions are
+ * exposed to resolve potential conflicts and apply the changes. The actual
+ * changes applied are notified through the callbacks
*/
/* forward declarations */
@@ -501,6 +507,81 @@ vlc_playlist_RemoveOne(vlc_playlist_t *playlist, size_t index)
}
/**
+ * Insert a list of media at a given index (if in range), or append.
+ *
+ * Contrary to vlc_playlist_Insert(), the index need not be in range: if it is
+ * out of bounds, items will be appended.
+ *
+ * This is an helper to apply a desynchronized insert request, i.e. the
+ * playlist content may have changed since the request had been submitted.
+ * This is typically the case for user requests (e.g. from UI), because the
+ * playlist lock has to be acquired *after* the user requested the
+ * change.
+ *
+ * \param playlist the playlist, locked
+ * \index index the index where the media are to be inserted
+ * \param media the array of media to insert
+ * \param count the number of media to insert
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API int
+vlc_playlist_RequestInsert(vlc_playlist_t *playlist, size_t index,
+ input_item_t *const media[], size_t count);
+
+/**
+ * Move a slice of items by value.
+ *
+ * If the indices are known, use vlc_playlist_Move() instead.
+ *
+ * This is an helper to apply a desynchronized move request, i.e. the playlist
+ * content may have changed since the request had been submitted. This is
+ * typically the case for user requests (e.g. from UI), because the playlist
+ * lock has to be acquired *after* the user requested the change.
+ *
+ * For optimization purpose, it is possible to pass an `index_hint`, which is
+ * the expected index of the first item of the slice (as known by the client).
+ * Hopefully, the index should often match, since conflicts are expected to be
+ * rare. Pass -1 not to pass any hint.
+ *
+ * \param playlist the playlist, locked
+ * \param items the array of items to move
+ * \param count the number of items to move
+ * \param target the new index of the moved slice
+ * \param index_hint the expected index of the first item (-1 for none)
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API int
+vlc_playlist_RequestMove(vlc_playlist_t *playlist,
+ vlc_playlist_item_t *const items[], size_t count,
+ size_t target, ssize_t index_hint);
+
+/**
+ * Remove a slice of items by value.
+ *
+ * If the indices are known, use vlc_playlist_Remove() instead.
+ *
+ * This is an helper to apply a desynchronized remove request, i.e. the
+ * playlist content may have changed since the request had been submitted.
+ * This is typically the case for user requests (e.g. from UI), because the
+ * playlist lock has to be acquired *after* the user requested the change.
+ *
+ * For optimization purpose, it is possible to pass an `index_hint`, which is
+ * the expected index of the first item of the slice (as known by the client).
+ * Hopefully, the index should often match, since conflicts are expected to be
+ * rare. Pass -1 not to pass any hint.
+ *
+ * \param playlist the playlist, locked
+ * \param items the array of items to remove
+ * \param count the number of items to remove
+ * \param index_hint the expected index of the first item (-1 for none)
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API int
+vlc_playlist_RequestRemove(vlc_playlist_t *playlist,
+ vlc_playlist_item_t *const items[], size_t count,
+ ssize_t index_hint);
+
+/**
* Return the index of a given item.
*
* \param playlist the playlist, locked
@@ -622,6 +703,30 @@ VLC_API int
vlc_playlist_GoTo(vlc_playlist_t *playlist, ssize_t index);
/**
+ * Go to a given item.
+ *
+ * If the index is known, use vlc_playlist_GoTo() instead.
+ *
+ * This is an helper to apply a desynchronized "go to" request, i.e. the
+ * playlist content may have changed since the request had been submitted.
+ * This is typically the case for user requests (e.g. from UI), because the
+ * playlist lock has to be acquired *after* the user requested the change.
+ *
+ * For optimization purpose, it is possible to pass an `index_hint`, which is
+ * the expected index of the first item of the slice (as known by the client).
+ * Hopefully, the index should often match, since conflicts are expected to be
+ * rare. Pass -1 not to pass any hint.
+ *
+ * \param playlist the playlist, locked
+ * \param item the item to go to (NULL for none)
+ * \param index_hint the expected index of the item (-1 for none)
+ * \return VLC_SUCCESS on success, another value on error
+ */
+VLC_API int
+vlc_playlist_RequestGoTo(vlc_playlist_t *playlist, vlc_playlist_item_t *item,
+ ssize_t index_hint);
+
+/**
* Return the player owned by the playlist.
*
* \param playlist the playlist (not necessarily locked)
diff --git a/src/Makefile.am b/src/Makefile.am
index 249bab700f..1b2d04a420 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -241,6 +241,7 @@ libvlccore_la_SOURCES = \
playlist/playlist.h \
playlist/preparse.c \
playlist/preparse.h \
+ playlist/request.c \
preparser/art.c \
preparser/art.h \
preparser/fetcher.c \
@@ -587,7 +588,8 @@ test_playlist_SOURCES = playlist/test.c \
playlist/notify.c \
playlist/player.c \
playlist/playlist.c \
- playlist/preparse.c
+ playlist/preparse.c \
+ playlist/request.c
test_playlist_CFLAGS = -DTEST_PLAYLIST
AM_LDFLAGS = -no-install
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 1769804b1e..b3f5f140d5 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -919,6 +919,9 @@ vlc_playlist_Clear
vlc_playlist_Insert
vlc_playlist_Move
vlc_playlist_Remove
+vlc_playlist_RequestInsert
+vlc_playlist_RequestMove
+vlc_playlist_RequestRemove
vlc_playlist_IndexOf
vlc_playlist_IndexOfMedia
vlc_playlist_GetPlaybackRepeat
@@ -931,6 +934,7 @@ vlc_playlist_HasNext
vlc_playlist_Prev
vlc_playlist_Next
vlc_playlist_GoTo
+vlc_playlist_RequestGoTo
vlc_playlist_GetPlayer
vlc_playlist_Start
vlc_playlist_Stop
diff --git a/src/playlist/request.c b/src/playlist/request.c
new file mode 100644
index 0000000000..808b88dbc6
--- /dev/null
+++ b/src/playlist/request.c
@@ -0,0 +1,265 @@
+/*****************************************************************************
+ * playlist/request.c
+ *****************************************************************************
+ * Copyright (C) 2018 VLC authors and VideoLAN
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser 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.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include "item.h"
+#include "playlist.h"
+
+#include <vlc_input_item.h>
+
+int
+vlc_playlist_RequestInsert(vlc_playlist_t *playlist, size_t index,
+ input_item_t *const media[], size_t count)
+{
+ vlc_playlist_AssertLocked(playlist);
+
+ size_t size = vlc_playlist_Count(playlist);
+ if (index > size)
+ index = size;
+
+ return vlc_playlist_Insert(playlist, index, media, count);
+}
+
+static ssize_t
+vlc_playlist_FindRealIndex(vlc_playlist_t *playlist, vlc_playlist_item_t *item,
+ ssize_t index_hint)
+{
+ if (index_hint != -1 && (size_t) index_hint < vlc_playlist_Count(playlist))
+ {
+ if (item == vlc_playlist_Get(playlist, index_hint))
+ /* we are lucky */
+ return index_hint;
+ }
+
+ /* we are unlucky, we need to find the item */
+ return vlc_playlist_IndexOf(playlist, item);
+}
+
+struct size_vector VLC_VECTOR(size_t);
+
+static void
+vlc_playlist_FindIndices(vlc_playlist_t *playlist,
+ vlc_playlist_item_t *const items[], size_t count,
+ ssize_t index_hint, struct size_vector *out)
+{
+ for (size_t i = 0; i < count; ++i)
+ {
+ ssize_t real_index = vlc_playlist_FindRealIndex(playlist, items[i],
+ index_hint);
+ if (real_index != -1)
+ {
+ int ok = vlc_vector_push(out, real_index);
+ assert(ok); /* cannot fail, space had been reserved */
+ VLC_UNUSED(ok);
+ /* the next item is expected after this one */
+ index_hint = real_index + 1;
+ }
+ }
+}
+
+static void
+vlc_playlist_RemoveBySlices(vlc_playlist_t *playlist, size_t sorted_indices[],
+ size_t count)
+{
+ assert(count > 0);
+ size_t last_index = sorted_indices[count - 1];
+ size_t slice_size = 1;
+ /* size_t is unsigned, take care not to test for non-negativity */
+ for (size_t i = count - 1; i != 0; --i)
+ {
+ size_t index = sorted_indices[i - 1];
+ if (index == last_index - 1)
+ slice_size++;
+ else
+ {
+ /* the previous slice is complete */
+ vlc_playlist_Remove(playlist, last_index, slice_size);
+ slice_size = 1;
+ }
+ last_index = index;
+ }
+ /* remove the last slice */
+ vlc_playlist_Remove(playlist, last_index, slice_size);
+}
+
+/**
+ * Move all items specified by their indices to form a contiguous slice, in
+ * order.
+ *
+ * \param playlist the playlist
+ * \param indices the indices of the items to regroup
+ * \param head_index the index where to prepend the group
+ * \return the start index of the resulting slice
+ */
+static size_t
+vlc_playlist_Regroup(vlc_playlist_t *playlist, size_t indices[],
+ size_t head_index)
+{
+ size_t head = indices[head_index];
+ if (head_index == 0)
+ /* nothing to regroup */
+ return head;
+
+ size_t slice_size = 1;
+ size_t last_index = indices[head_index - 1];
+
+ /* size_t is unsigned, take care not to test for non-negativity */
+ for (size_t i = head_index - 1; i != 0; --i)
+ {
+ size_t index = indices[i - 1];
+ if (index == last_index - 1)
+ slice_size++;
+ else
+ {
+ assert(last_index != head);
+ if (last_index < head)
+ {
+ assert(head >= slice_size);
+ head -= slice_size;
+
+ /* update index of items that will be moved as a side-effect */
+ for (size_t j = 0; j <= i; ++j)
+ if (indices[j] >= last_index + slice_size && indices[j] < head)
+ indices[j] -= slice_size;
+ }
+ else
+ {
+ /* update index of items that will be moved as a side-effect */
+ for (size_t j = 0; j <= i; ++j)
+ if (indices[j] >= head && indices[j] < last_index)
+ indices[j] += slice_size;
+ }
+ index = indices[i - 1]; /* current index might have been updated */
+
+ /* the slice is complete, move it to build the unique slice */
+ vlc_playlist_Move(playlist, last_index, slice_size, head);
+ slice_size = 1;
+ }
+
+ last_index = index;
+ }
+
+ /* move the last slice to build the unique slice */
+ if (last_index < head)
+ {
+ assert(head >= slice_size);
+ head -= slice_size;
+ }
+ vlc_playlist_Move(playlist, last_index, slice_size, head);
+ return head;
+}
+
+static void
+vlc_playlist_MoveBySlices(vlc_playlist_t *playlist, size_t indices[],
+ size_t count, size_t target)
+{
+ assert(count > 0);
+
+ /* pass the last slice */
+ size_t i;
+ for (i = count - 1; i != 0; --i)
+ if (indices[i - 1] != indices[i] - 1)
+ break;
+
+ /* regroup items to form a unique slice */
+ size_t head = vlc_playlist_Regroup(playlist, indices, i);
+
+ /* move the unique slice to the requested target */
+ if (head != target)
+ vlc_playlist_Move(playlist, head, count, target);
+}
+
+static int
+cmp_size(const void *lhs, const void *rhs)
+{
+ size_t a = *(size_t *) lhs;
+ size_t b = *(size_t *) rhs;
+ if (a < b)
+ return -1;
+ if (a == b)
+ return 0;
+ return 1;
+}
+
+int
+vlc_playlist_RequestMove(vlc_playlist_t *playlist,
+ vlc_playlist_item_t *const items[], size_t count,
+ size_t target, ssize_t index_hint)
+{
+ vlc_playlist_AssertLocked(playlist);
+
+ struct size_vector vector = VLC_VECTOR_INITIALIZER;
+ if (!vlc_vector_reserve(&vector, count))
+ return VLC_ENOMEM;
+
+ vlc_playlist_FindIndices(playlist, items, count, index_hint, &vector);
+
+ if (vector.size > 0)
+ {
+ size_t size = vlc_playlist_Count(playlist);
+ if (target >= size)
+ target = size - 1;
+
+ /* keep the items in the same order as the request (do not sort them) */
+ vlc_playlist_MoveBySlices(playlist, vector.data, vector.size, target);
+ }
+
+ vlc_vector_destroy(&vector);
+ return VLC_SUCCESS;
+}
+
+int
+vlc_playlist_RequestRemove(vlc_playlist_t *playlist,
+ vlc_playlist_item_t *const items[], size_t count,
+ ssize_t index_hint)
+{
+ vlc_playlist_AssertLocked(playlist);
+
+ struct size_vector vector = VLC_VECTOR_INITIALIZER;
+ if (!vlc_vector_reserve(&vector, count))
+ return VLC_ENOMEM;
+
+ vlc_playlist_FindIndices(playlist, items, count, index_hint, &vector);
+
+ if (vector.size > 0)
+ {
+ /* sort so that removing an item does not shift the other indices */
+ qsort(vector.data, vector.size, sizeof(vector.data[0]), cmp_size);
+
+ vlc_playlist_RemoveBySlices(playlist, vector.data, vector.size);
+ }
+
+ vlc_vector_destroy(&vector);
+ return VLC_SUCCESS;
+}
+
+int
+vlc_playlist_RequestGoTo(vlc_playlist_t *playlist, vlc_playlist_item_t *item,
+ ssize_t index_hint)
+{
+ vlc_playlist_AssertLocked(playlist);
+ ssize_t real_index = item
+ ? vlc_playlist_FindRealIndex(playlist, item, index_hint)
+ : -1;
+ return vlc_playlist_GoTo(playlist, real_index);
+}
diff --git a/src/playlist/test.c b/src/playlist/test.c
index 9f8143d504..a41d007e2c 100644
--- a/src/playlist/test.c
+++ b/src/playlist/test.c
@@ -1265,6 +1265,594 @@ test_goto(void)
vlc_playlist_Delete(playlist);
}
+static void
+test_request_insert(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[5];
+ CreateDummyMediaArray(media, 5);
+
+ /* initial playlist with 3 items */
+ int ret = vlc_playlist_Append(playlist, media, 3);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_added = callback_on_items_added,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ /* insert 5 items at index 10 (out-of-bounds) */
+ ret = vlc_playlist_RequestInsert(playlist, 10, &media[3], 2);
+ assert(ret == VLC_SUCCESS);
+
+ assert(vlc_playlist_Count(playlist) == 5);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 2);
+ EXPECT_AT(3, 3);
+ EXPECT_AT(4, 4);
+
+ assert(ctx.vec_items_added.size == 1);
+ assert(ctx.vec_items_added.data[0].index == 3); /* index was changed */
+ assert(ctx.vec_items_added.data[0].count == 2);
+ assert(ctx.vec_items_added.data[0].state.playlist_size == 5);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 5);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_request_remove_with_matching_hint(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_removed = callback_on_items_removed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_item_t *items_to_remove[] = {
+ vlc_playlist_Get(playlist, 3),
+ vlc_playlist_Get(playlist, 4),
+ vlc_playlist_Get(playlist, 5),
+ vlc_playlist_Get(playlist, 6),
+ };
+
+ ret = vlc_playlist_RequestRemove(playlist, items_to_remove, 4, 3);
+ assert(ret == VLC_SUCCESS);
+
+ assert(vlc_playlist_Count(playlist) == 6);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 2);
+ EXPECT_AT(3, 7);
+ EXPECT_AT(4, 8);
+ EXPECT_AT(5, 9);
+
+ assert(ctx.vec_items_removed.size == 1);
+ assert(ctx.vec_items_removed.data[0].index == 3);
+ assert(ctx.vec_items_removed.data[0].count == 4);
+ assert(ctx.vec_items_removed.data[0].state.playlist_size == 6);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_request_remove_without_hint(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_removed = callback_on_items_removed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_item_t *items_to_remove[] = {
+ vlc_playlist_Get(playlist, 3),
+ vlc_playlist_Get(playlist, 4),
+ vlc_playlist_Get(playlist, 5),
+ vlc_playlist_Get(playlist, 6),
+ };
+
+ ret = vlc_playlist_RequestRemove(playlist, items_to_remove, 4, -1);
+ assert(ret == VLC_SUCCESS);
+
+ assert(vlc_playlist_Count(playlist) == 6);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 2);
+ EXPECT_AT(3, 7);
+ EXPECT_AT(4, 8);
+ EXPECT_AT(5, 9);
+
+ assert(ctx.vec_items_removed.size == 1);
+ assert(ctx.vec_items_removed.data[0].index == 3);
+ assert(ctx.vec_items_removed.data[0].count == 4);
+ assert(ctx.vec_items_removed.data[0].state.playlist_size == 6);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_request_remove_adapt(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[11];
+ CreateDummyMediaArray(media, 11);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_removed = callback_on_items_removed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_item_t *dummy = vlc_playlist_item_New(media[10]);
+ assert(dummy);
+
+ /* remove items in a wrong order at wrong position, as if the playlist had
+ * been sorted/shuffled before the request were applied */
+ vlc_playlist_item_t *items_to_remove[] = {
+ vlc_playlist_Get(playlist, 3),
+ vlc_playlist_Get(playlist, 2),
+ vlc_playlist_Get(playlist, 6),
+ vlc_playlist_Get(playlist, 9),
+ vlc_playlist_Get(playlist, 1),
+ dummy, /* inexistant */
+ vlc_playlist_Get(playlist, 8),
+ };
+
+ ret = vlc_playlist_RequestRemove(playlist, items_to_remove, 7, 3);
+ assert(ret == VLC_SUCCESS);
+
+ vlc_playlist_item_Release(dummy);
+
+ assert(vlc_playlist_Count(playlist) == 4);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 4);
+ EXPECT_AT(2, 5);
+ EXPECT_AT(3, 7);
+
+ /* it should notify 3 different slices removed, in descending order for
+ * optimization: {8,9}, {6}, {1,2,3}. */
+
+ assert(ctx.vec_items_removed.size == 3);
+
+ assert(ctx.vec_items_removed.data[0].index == 8);
+ assert(ctx.vec_items_removed.data[0].count == 2);
+ assert(ctx.vec_items_removed.data[0].state.playlist_size == 8);
+
+ assert(ctx.vec_items_removed.data[1].index == 6);
+ assert(ctx.vec_items_removed.data[1].count == 1);
+ assert(ctx.vec_items_removed.data[1].state.playlist_size == 7);
+
+ assert(ctx.vec_items_removed.data[2].index == 1);
+ assert(ctx.vec_items_removed.data[2].count == 3);
+ assert(ctx.vec_items_removed.data[2].state.playlist_size == 4);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 11);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_request_move_with_matching_hint(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_moved = callback_on_items_moved,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_item_t *items_to_move[] = {
+ vlc_playlist_Get(playlist, 5),
+ vlc_playlist_Get(playlist, 6),
+ vlc_playlist_Get(playlist, 7),
+ vlc_playlist_Get(playlist, 8),
+ };
+
+ ret = vlc_playlist_RequestMove(playlist, items_to_move, 4, 2, 5);
+ assert(ret == VLC_SUCCESS);
+
+ assert(vlc_playlist_Count(playlist) == 10);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 5);
+ EXPECT_AT(3, 6);
+ EXPECT_AT(4, 7);
+ EXPECT_AT(5, 8);
+ EXPECT_AT(6, 2);
+ EXPECT_AT(7, 3);
+ EXPECT_AT(8, 4);
+ EXPECT_AT(9, 9);
+
+ assert(ctx.vec_items_moved.size == 1);
+ assert(ctx.vec_items_moved.data[0].index == 5);
+ assert(ctx.vec_items_moved.data[0].count == 4);
+ assert(ctx.vec_items_moved.data[0].state.playlist_size == 10);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_request_move_without_hint(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_moved = callback_on_items_moved,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_item_t *items_to_move[] = {
+ vlc_playlist_Get(playlist, 5),
+ vlc_playlist_Get(playlist, 6),
+ vlc_playlist_Get(playlist, 7),
+ vlc_playlist_Get(playlist, 8),
+ };
+
+ ret = vlc_playlist_RequestMove(playlist, items_to_move, 4, 2, -1);
+ assert(ret == VLC_SUCCESS);
+
+ assert(vlc_playlist_Count(playlist) == 10);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 5);
+ EXPECT_AT(3, 6);
+ EXPECT_AT(4, 7);
+ EXPECT_AT(5, 8);
+ EXPECT_AT(6, 2);
+ EXPECT_AT(7, 3);
+ EXPECT_AT(8, 4);
+ EXPECT_AT(9, 9);
+
+ assert(ctx.vec_items_moved.size == 1);
+ assert(ctx.vec_items_moved.data[0].index == 5);
+ assert(ctx.vec_items_moved.data[0].count == 4);
+ assert(ctx.vec_items_moved.data[0].state.playlist_size == 10);
+
+ vlc_playlist_item_t *item = vlc_playlist_Get(playlist, 3);
+ /* move it to index 42 (out of bounds) */
+ vlc_playlist_RequestMove(playlist, &item, 1, 42, -1);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 1);
+ EXPECT_AT(2, 5);
+ EXPECT_AT(3, 7);
+ EXPECT_AT(4, 8);
+ EXPECT_AT(5, 2);
+ EXPECT_AT(6, 3);
+ EXPECT_AT(7, 4);
+ EXPECT_AT(8, 9);
+ EXPECT_AT(9, 6);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+static void
+test_request_move_adapt(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[16];
+ CreateDummyMediaArray(media, 16);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 15);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_items_moved = callback_on_items_moved,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ vlc_playlist_item_t *dummy = vlc_playlist_item_New(media[15]);
+ assert(dummy);
+
+ /* move items in a wrong order at wrong position, as if the playlist had
+ * been sorted/shuffled before the request were applied */
+ vlc_playlist_item_t *items_to_move[] = {
+ vlc_playlist_Get(playlist, 7),
+ vlc_playlist_Get(playlist, 8),
+ vlc_playlist_Get(playlist, 5),
+ vlc_playlist_Get(playlist, 12),
+ dummy, /* inexistant */
+ vlc_playlist_Get(playlist, 3),
+ vlc_playlist_Get(playlist, 13),
+ vlc_playlist_Get(playlist, 14),
+ vlc_playlist_Get(playlist, 1),
+ };
+
+ vlc_playlist_RequestMove(playlist, items_to_move, 9, 3, 2);
+
+ vlc_playlist_item_Release(dummy);
+
+ assert(vlc_playlist_Count(playlist) == 15);
+
+ EXPECT_AT(0, 0);
+ EXPECT_AT(1, 2);
+ EXPECT_AT(2, 4);
+
+ EXPECT_AT(3, 7);
+ EXPECT_AT(4, 8);
+ EXPECT_AT(5, 5);
+ EXPECT_AT(6, 12);
+ EXPECT_AT(7, 3);
+ EXPECT_AT(8, 13);
+ EXPECT_AT(9, 14);
+ EXPECT_AT(10, 1);
+
+ EXPECT_AT(11, 6);
+ EXPECT_AT(12, 9);
+ EXPECT_AT(13, 10);
+ EXPECT_AT(14, 11);
+
+ /* there are 6 slices to move: 7-8, 5, 12, 3, 13-14, 1 */
+ assert(ctx.vec_items_moved.size == 6);
+
+ struct VLC_VECTOR(int) vec = VLC_VECTOR_INITIALIZER;
+ for (int i = 0; i < 15; ++i)
+ vlc_vector_push(&vec, i * 10);
+
+ struct items_moved_report report;
+ vlc_vector_foreach(report, &ctx.vec_items_moved)
+ /* apply the changes as reported by the callbacks */
+ vlc_vector_move_slice(&vec, report.index, report.count, report.target);
+
+ /* the vector items must have been moved the same way as the playlist */
+ assert(vec.size == 15);
+ assert(vec.data[0] == 0);
+ assert(vec.data[1] == 20);
+ assert(vec.data[2] == 40);
+ assert(vec.data[3] == 70);
+ assert(vec.data[4] == 80);
+ assert(vec.data[5] == 50);
+ assert(vec.data[6] == 120);
+ assert(vec.data[7] == 30);
+ assert(vec.data[8] == 130);
+ assert(vec.data[9] == 140);
+ assert(vec.data[10] == 10);
+ assert(vec.data[11] == 60);
+ assert(vec.data[12] == 90);
+ assert(vec.data[13] == 100);
+ assert(vec.data[14] == 110);
+
+ vlc_vector_destroy(&vec);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 16);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_request_goto_with_matching_hint(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ /* go to an item in the middle, with incorrect index_hint */
+ vlc_playlist_item_t *item = vlc_playlist_Get(playlist, 4);
+ ret = vlc_playlist_RequestGoTo(playlist, item, 4);
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 4);
+ assert(playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 4);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
+static void
+test_request_goto_without_hint(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ /* go to an item in the middle, with incorrect index_hint */
+ vlc_playlist_item_t *item = vlc_playlist_Get(playlist, 4);
+ ret = vlc_playlist_RequestGoTo(playlist, item, -1); /* no hint */
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 4);
+ assert(playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 4);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+static void
+test_request_goto_adapt(void)
+{
+ vlc_playlist_t *playlist = vlc_playlist_New(NULL);
+ assert(playlist);
+
+ input_item_t *media[10];
+ CreateDummyMediaArray(media, 10);
+
+ /* initial playlist with 10 items */
+ int ret = vlc_playlist_Append(playlist, media, 10);
+ assert(ret == VLC_SUCCESS);
+
+ struct vlc_playlist_callbacks cbs = {
+ .on_current_index_changed = callback_on_current_index_changed,
+ .on_has_prev_changed = callback_on_has_prev_changed,
+ .on_has_next_changed = callback_on_has_next_changed,
+ };
+
+ struct callback_ctx ctx = CALLBACK_CTX_INITIALIZER;
+ vlc_playlist_listener_id *listener =
+ vlc_playlist_AddListener(playlist, &cbs, &ctx);
+ assert(listener);
+
+ /* go to an item in the middle, with incorrect index_hint */
+ vlc_playlist_item_t *item = vlc_playlist_Get(playlist, 4);
+ ret = vlc_playlist_RequestGoTo(playlist, item, 7); /* wrong index hint */
+ assert(ret == VLC_SUCCESS);
+
+ assert(playlist->current == 4);
+ assert(playlist->has_prev);
+ assert(playlist->has_next);
+
+ assert(ctx.vec_current_index_changed.size == 1);
+ assert(ctx.vec_current_index_changed.data[0].current == 4);
+
+ assert(ctx.vec_has_prev_changed.size == 1);
+ assert(ctx.vec_has_prev_changed.data[0].has_prev);
+
+ assert(ctx.vec_has_next_changed.size == 0);
+
+ callback_ctx_destroy(&ctx);
+ vlc_playlist_RemoveListener(playlist, listener);
+ DestroyMediaArray(media, 10);
+ vlc_playlist_Delete(playlist);
+}
+
#undef EXPECT_AT
int main(void)
@@ -1285,5 +1873,15 @@ int main(void)
test_prev();
test_next();
test_goto();
+ test_request_insert();
+ test_request_remove_with_matching_hint();
+ test_request_remove_without_hint();
+ test_request_remove_adapt();
+ test_request_move_with_matching_hint();
+ test_request_move_without_hint();
+ test_request_move_adapt();
+ test_request_goto_with_matching_hint();
+ test_request_goto_without_hint();
+ test_request_goto_adapt();
return 0;
}
More information about the vlc-commits
mailing list