From 13208bf5a7c91a6406195139f1068f173ccdac94 Mon Sep 17 00:00:00 2001
From: Jeffrey Middleton <jeffrey.r.middleton@nasa.gov>
Date: Thu, 26 Mar 2009 22:02:56 +0100
Subject: [PATCH] queue/playlist/command: move range

The move command now accepts a range for the first argument, in the same
form as other range commands, e.g. move 15:17 3.  The first song in the
range is placed at the destination position. Note that as with other
range commands, the range is inclusive on the left only; this example
would move only songs 15 and 16, not 17.

[mk: fixed signed/unsigned warnings; use G_MAXUINT instead of
UINT_MAX]
---
 doc/protocol.xml    | 10 +++++++---
 src/command.c       |  8 +++++---
 src/playlist.h      |  2 +-
 src/playlist_edit.c | 31 ++++++++++++++++++-------------
 src/queue.c         | 42 ++++++++++++++++++++++++++++++++++++++++++
 src/queue.h         |  6 ++++++
 6 files changed, 79 insertions(+), 20 deletions(-)

diff --git a/doc/protocol.xml b/doc/protocol.xml
index 409f9ca0e..8f5b4b9e8 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -634,14 +634,18 @@ OK
           <term>
             <cmdsynopsis>
               <command>move</command>
-              <arg choice="req"><replaceable>FROM</replaceable></arg>
+              <group>
+                  <arg choice="req"><replaceable>FROM</replaceable></arg>
+                  <arg choice="req"><replaceable>START:END</replaceable></arg>
+              </group>
               <arg choice="req"><replaceable>TO</replaceable></arg>
             </cmdsynopsis>
           </term>
           <listitem>
             <para>
-              Moves the song at <varname>FROM</varname> to
-              <varname>TO</varname> in the playlist.
+              Moves the song at <varname>FROM</varname> or range of songs
+              at <varname>START:END</varname> to <varname>TO</varname>
+              in the playlist.
             </para>
           </listitem>
         </varlistentry>
diff --git a/src/command.c b/src/command.c
index 7603f669c..1b8c45f26 100644
--- a/src/command.c
+++ b/src/command.c
@@ -1181,14 +1181,16 @@ handle_list(struct client *client, int argc, char *argv[])
 static enum command_return
 handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
 {
-	int from, to;
+	unsigned start, end;
+	int to;
 	enum playlist_result result;
 
-	if (!check_int(client, &from, argv[1], check_integer, argv[1]))
+	if (!check_range(client, &start, &end,
+				      argv[1], need_range))
 		return COMMAND_RETURN_ERROR;
 	if (!check_int(client, &to, argv[2], check_integer, argv[2]))
 		return COMMAND_RETURN_ERROR;
-	result = moveSongInPlaylist(&g_playlist, from, to);
+	result = moveSongRangeInPlaylist(&g_playlist, start, end, to);
 	return print_playlist_result(client, result);
 }
 
diff --git a/src/playlist.h b/src/playlist.h
index efe13b0b8..41befe455 100644
--- a/src/playlist.h
+++ b/src/playlist.h
@@ -158,7 +158,7 @@ void
 deleteASongFromPlaylist(struct playlist *playlist, const struct song *song);
 
 enum playlist_result
-moveSongInPlaylist(struct playlist *playlist, unsigned from, int to);
+moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to);
 
 enum playlist_result
 moveSongInPlaylistById(struct playlist *playlist, unsigned id, int to);
diff --git a/src/playlist_edit.c b/src/playlist_edit.c
index 8580a5030..b83dc0933 100644
--- a/src/playlist_edit.c
+++ b/src/playlist_edit.c
@@ -290,19 +290,21 @@ deleteASongFromPlaylist(struct playlist *playlist, const struct song *song)
 }
 
 enum playlist_result
-moveSongInPlaylist(struct playlist *playlist, unsigned from, int to)
+moveSongRangeInPlaylist(struct playlist *playlist, unsigned start, unsigned end, int to)
 {
 	const struct song *queued;
 	int currentSong;
 
-	if (!queue_valid_position(&playlist->queue, from))
+	if (!queue_valid_position(&playlist->queue, start) ||
+		!queue_valid_position(&playlist->queue, end - 1))
 		return PLAYLIST_RESULT_BAD_RANGE;
 
-	if ((to >= 0 && to >= (int)queue_length(&playlist->queue)) ||
+	if ((to >= 0 && to + end - start - 1 >= queue_length(&playlist->queue)) ||
 	    (to < 0 && abs(to) > (int)queue_length(&playlist->queue)))
 		return PLAYLIST_RESULT_BAD_RANGE;
 
-	if ((int)from == to) /* no-op */
+	if ((int)start == to)
+		/* nothing happens */
 		return PLAYLIST_RESULT_SUCCESS;
 
 	queued = playlist_get_queued_song(playlist);
@@ -316,24 +318,27 @@ moveSongInPlaylist(struct playlist *playlist, unsigned from, int to)
 					      playlist->current)
 		: -1;
 	if (to < 0 && playlist->current >= 0) {
-		if ((unsigned)currentSong == from)
+		if (start <= (unsigned)currentSong && (unsigned)currentSong <= end)
 			/* no-op, can't be moved to offset of itself */
 			return PLAYLIST_RESULT_SUCCESS;
 		to = (currentSong + abs(to)) % queue_length(&playlist->queue);
+		if (start < (unsigned)to)
+			to--;
 	}
 
-	queue_move(&playlist->queue, from, to);
+	queue_move_range(&playlist->queue, start, end, to);
 
 	if (!playlist->queue.random) {
 		/* update current/queued */
-		if (playlist->current == (int)from)
-			playlist->current = to;
-		else if (playlist->current > (int)from &&
+		if ((int)start <= playlist->current &&
+		    (unsigned)playlist->current < end)
+			playlist->current += to - start;
+		else if (playlist->current >= (int)end &&
 			 playlist->current <= to) {
-			playlist->current--;
+			playlist->current -= end - start;
 		} else if (playlist->current >= to &&
-			   playlist->current < (int)from) {
-			playlist->current++;
+			   playlist->current < (int)start) {
+			playlist->current += end - start;
 		}
 	}
 
@@ -351,7 +356,7 @@ moveSongInPlaylistById(struct playlist *playlist, unsigned id1, int to)
 	if (song < 0)
 		return PLAYLIST_RESULT_NO_SUCH_SONG;
 
-	return moveSongInPlaylist(playlist, song, to);
+	return moveSongRangeInPlaylist(playlist, song, song+1, to);
 }
 
 void shufflePlaylist(struct playlist *playlist, unsigned start, unsigned end)
diff --git a/src/queue.c b/src/queue.c
index 581e5c566..b9486cfe5 100644
--- a/src/queue.c
+++ b/src/queue.c
@@ -172,6 +172,48 @@ queue_move(struct queue *queue, unsigned from, unsigned to)
 	}
 }
 
+void
+queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to)
+{
+	struct queue_item items[end - start];
+	// Copy the original block [start,end-1]
+	for (unsigned i = start; i < end; i++)
+		items[i - start] = queue->items[i];
+
+	// If to > start, we need to move to-start items to start, starting from end
+	for (unsigned i = end; i < end + to - start; i++)
+		queue_move_song_to(queue, i, start + i - end);
+
+	// If to < start, we need to move start-to items to newend (= end + to - start), starting from to
+	// This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1
+	// We have to iterate in this order to avoid writing over something we haven't yet moved
+	for (unsigned i = start - 1; i >= to && i != G_MAXUINT; i--)
+		queue_move_song_to(queue, i, i + end - start);
+
+	// Copy the original block back in, starting at to.
+	for (unsigned i = start; i< end; i++)
+	{
+		queue->idToPosition[items[i-start].id] = to + i - start;
+		queue->items[to + i - start] = items[i-start];
+		queue->items[to + i - start].version = queue->version;
+	}
+
+	if (queue->random) {
+		// Update the positions in the queue.
+		// Note that the ranges for these cases are the same as the ranges of
+		// the loops above.
+		for (unsigned i = 0; i < queue->length; i++) {
+			if (queue->order[i] >= end && queue->order[i] < to + end - start)
+				queue->order[i] -= end - start;
+			else if (queue->order[i] < start &&
+				 queue->order[i] >= to)
+				queue->order[i] += end - start;
+			else if (start <= queue->order[i] && queue->order[i] < end)
+				queue->order[i] += to - start;
+		}
+	}
+}
+
 void
 queue_delete(struct queue *queue, unsigned position)
 {
diff --git a/src/queue.h b/src/queue.h
index 402c6bd26..7f46c2e19 100644
--- a/src/queue.h
+++ b/src/queue.h
@@ -291,6 +291,12 @@ queue_swap_order(struct queue *queue, unsigned order1, unsigned order2)
 void
 queue_move(struct queue *queue, unsigned from, unsigned to);
 
+/**
+ * Moves a range of songs to a new position.
+ */
+void
+queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to);
+
 /**
  * Removes a song from the playlist.
  */