PlaylistEdit.cxx 9.72 KB
Newer Older
1
/*
2
 * Copyright (C) 2003-2013 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13
 * http://www.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.
14 15 16 17
 *
 * 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.
18 19 20 21 22 23 24 25
 */

/*
 * Functions for editing the playlist (adding, removing, reordering
 * songs in the queue).
 *
 */

26
#include "config.h"
27
#include "Playlist.hxx"
28
#include "PlaylistError.hxx"
29
#include "PlayerControl.hxx"
Max Kellermann's avatar
Max Kellermann committed
30
#include "util/UriUtil.hxx"
31
#include "util/Error.hxx"
32
#include "Song.hxx"
Max Kellermann's avatar
Max Kellermann committed
33
#include "Idle.hxx"
34 35
#include "DatabaseGlue.hxx"
#include "DatabasePlugin.hxx"
36
#include "Log.hxx"
37

38 39
#include <stdlib.h>

40 41
void
playlist::OnModified()
42
{
43
	queue.IncrementVersion();
44 45 46 47

	idle_add(IDLE_PLAYLIST);
}

48
void
49
playlist::Clear(player_control &pc)
50
{
51
	Stop(pc);
52

53 54
	queue.Clear();
	current = -1;
55

56
	OnModified();
57 58 59
}

enum playlist_result
60
playlist::AppendFile(struct player_control &pc,
61
		     const char *path_utf8, unsigned *added_id)
62
{
63
	Song *song = Song::LoadFile(path_utf8, nullptr);
64
	if (song == nullptr)
65 66
		return PLAYLIST_RESULT_NO_SUCH_SONG;

67 68 69
	const auto result = AppendSong(pc, song, added_id);
	song->Free();
	return result;
70 71 72
}

enum playlist_result
73
playlist::AppendSong(struct player_control &pc,
74
		     Song *song, unsigned *added_id)
75 76 77
{
	unsigned id;

78
	if (queue.IsFull())
79 80
		return PLAYLIST_RESULT_TOO_LARGE;

81
	const Song *const queued_song = GetQueuedSong();
82

83
	id = queue.Append(song, 0);
84

85
	if (queue.random) {
86 87 88 89
		/* shuffle the new song into the list of remaining
		   songs to play */

		unsigned start;
90 91
		if (queued >= 0)
			start = queued + 1;
92
		else
93 94 95
			start = current + 1;
		if (start < queue.GetLength())
			queue.ShuffleOrderLast(start, queue.GetLength());
96 97
	}

98 99
	UpdateQueuedSong(pc, queued_song);
	OnModified();
100 101 102 103 104 105 106 107

	if (added_id)
		*added_id = id;

	return PLAYLIST_RESULT_SUCCESS;
}

enum playlist_result
108
playlist::AppendURI(struct player_control &pc,
109
		    const char *uri, unsigned *added_id)
Max Kellermann's avatar
Max Kellermann committed
110
{
111
	FormatDebug(playlist_domain, "add to playlist: %s", uri);
Max Kellermann's avatar
Max Kellermann committed
112

113
	const Database *db = nullptr;
114
	Song *song;
115
	if (uri_has_scheme(uri)) {
116
		song = Song::NewRemote(uri);
117
	} else {
118
		db = GetDatabase(IgnoreError());
119 120 121
		if (db == nullptr)
			return PLAYLIST_RESULT_NO_SUCH_SONG;

122
		song = db->GetSong(uri, IgnoreError());
123 124 125
		if (song == nullptr)
			return PLAYLIST_RESULT_NO_SUCH_SONG;
	}
Max Kellermann's avatar
Max Kellermann committed
126

127
	enum playlist_result result = AppendSong(pc, song, added_id);
128 129
	if (db != nullptr)
		db->ReturnSong(song);
130 131
	else
		song->Free();
132 133

	return result;
Max Kellermann's avatar
Max Kellermann committed
134 135 136
}

enum playlist_result
137
playlist::SwapPositions(player_control &pc, unsigned song1, unsigned song2)
138
{
139
	if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2))
140 141
		return PLAYLIST_RESULT_BAD_RANGE;

142
	const Song *const queued_song = GetQueuedSong();
143

144
	queue.SwapPositions(song1, song2);
145

146 147
	if (queue.random) {
		/* update the queue order, so that current
148 149
		   still points to the current song order */

150 151
		queue.SwapOrders(queue.PositionToOrder(song1),
				 queue.PositionToOrder(song2));
152 153 154
	} else {
		/* correct the "current" song order */

155 156 157 158
		if (current == (int)song1)
			current = song2;
		else if (current == (int)song2)
			current = song1;
159 160
	}

161 162
	UpdateQueuedSong(pc, queued_song);
	OnModified();
163 164 165 166 167

	return PLAYLIST_RESULT_SUCCESS;
}

enum playlist_result
168
playlist::SwapIds(player_control &pc, unsigned id1, unsigned id2)
169
{
170 171
	int song1 = queue.IdToPosition(id1);
	int song2 = queue.IdToPosition(id2);
172 173 174 175

	if (song1 < 0 || song2 < 0)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

176
	return SwapPositions(pc, song1, song2);
177 178
}

179
enum playlist_result
180 181 182
playlist::SetPriorityRange(player_control &pc,
			   unsigned start, unsigned end,
			   uint8_t priority)
183
{
184
	if (start >= GetLength())
185 186
		return PLAYLIST_RESULT_BAD_RANGE;

187 188
	if (end > GetLength())
		end = GetLength();
189 190 191 192 193 194

	if (start >= end)
		return PLAYLIST_RESULT_SUCCESS;

	/* remember "current" and "queued" */

195
	const int current_position = GetCurrentPosition();
196
	const Song *const queued_song = GetQueuedSong();
197 198 199

	/* apply the priority changes */

200
	queue.SetPriorityRange(start, end, priority, current);
201 202 203 204

	/* restore "current" and choose a new "queued" */

	if (current_position >= 0)
205
		current = queue.PositionToOrder(current_position);
206

207 208
	UpdateQueuedSong(pc, queued_song);
	OnModified();
209 210 211 212 213

	return PLAYLIST_RESULT_SUCCESS;
}

enum playlist_result
214 215
playlist::SetPriorityId(struct player_control &pc,
			unsigned song_id, uint8_t priority)
216
{
217
	int song_position = queue.IdToPosition(song_id);
218 219 220
	if (song_position < 0)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

221 222
	return SetPriorityRange(pc, song_position, song_position + 1,
				priority);
223 224 225

}

226 227
void
playlist::DeleteInternal(player_control &pc,
228
			 unsigned song, const Song **queued_p)
229
{
230
	assert(song < GetLength());
231

232
	unsigned songOrder = queue.PositionToOrder(song);
233

234
	if (playing && current == (int)songOrder) {
235
		const bool paused = pc.GetState() == PlayerState::PAUSE;
236 237 238

		/* the current song is going to be deleted: stop the player */

239
		pc.Stop();
240
		playing = false;
241 242 243

		/* see which song is going to be played instead */

244 245 246
		current = queue.GetNextOrder(current);
		if (current == (int)songOrder)
			current = -1;
247

248
		if (current >= 0 && !paused)
249
			/* play the song after the deleted one */
250
			PlayOrder(pc, current);
251 252 253
		else
			/* no songs left to play, stop playback
			   completely */
254
			Stop(pc);
255

256
		*queued_p = nullptr;
257
	} else if (current == (int)songOrder)
258 259
		/* there's a "current song" but we're not playing
		   currently - clear "current" */
260
		current = -1;
261 262 263

	/* now do it: remove the song */

264
	queue.DeletePosition(song);
265 266 267

	/* update the "current" and "queued" variables */

268 269
	if (current > (int)songOrder)
		current--;
270 271 272
}

enum playlist_result
273
playlist::DeletePosition(struct player_control &pc, unsigned song)
274
{
275
	if (song >= queue.GetLength())
276 277
		return PLAYLIST_RESULT_BAD_RANGE;

278
	const Song *queued_song = GetQueuedSong();
279

280
	DeleteInternal(pc, song, &queued_song);
281

282 283
	UpdateQueuedSong(pc, queued_song);
	OnModified();
284 285 286 287

	return PLAYLIST_RESULT_SUCCESS;
}

288
enum playlist_result
289
playlist::DeleteRange(struct player_control &pc, unsigned start, unsigned end)
290
{
291
	if (start >= queue.GetLength())
292 293
		return PLAYLIST_RESULT_BAD_RANGE;

294 295
	if (end > queue.GetLength())
		end = queue.GetLength();
296 297 298 299

	if (start >= end)
		return PLAYLIST_RESULT_SUCCESS;

300
	const Song *queued_song = GetQueuedSong();
301 302

	do {
303
		DeleteInternal(pc, --end, &queued_song);
304 305
	} while (end != start);

306 307
	UpdateQueuedSong(pc, queued_song);
	OnModified();
308 309 310 311

	return PLAYLIST_RESULT_SUCCESS;
}

312
enum playlist_result
313
playlist::DeleteId(struct player_control &pc, unsigned id)
314
{
315
	int song = queue.IdToPosition(id);
316 317 318
	if (song < 0)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

319
	return DeletePosition(pc, song);
320 321 322
}

void
323
playlist::DeleteSong(struct player_control &pc, const struct Song &song)
324
{
325 326 327 328
	for (int i = queue.GetLength() - 1; i >= 0; --i)
		// TODO: compare URI instead of pointer
		if (&song == queue.Get(i))
			DeletePosition(pc, i);
329 330 331
}

enum playlist_result
332
playlist::MoveRange(player_control &pc, unsigned start, unsigned end, int to)
333
{
334
	if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1))
335 336
		return PLAYLIST_RESULT_BAD_RANGE;

337 338
	if ((to >= 0 && to + end - start - 1 >= GetLength()) ||
	    (to < 0 && unsigned(abs(to)) > GetLength()))
339 340
		return PLAYLIST_RESULT_BAD_RANGE;

341 342
	if ((int)start == to)
		/* nothing happens */
343 344
		return PLAYLIST_RESULT_SUCCESS;

345
	const Song *const queued_song = GetQueuedSong();
346 347 348 349 350

	/*
	 * (to < 0) => move to offset from current song
	 * (-playlist.length == to) => move to position BEFORE current song
	 */
351
	const int currentSong = GetCurrentPosition();
352 353 354 355 356 357
	if (to < 0) {
		if (currentSong < 0)
			/* can't move relative to current song,
			   because there is no current song */
			return PLAYLIST_RESULT_BAD_RANGE;

358
		if (start <= (unsigned)currentSong && (unsigned)currentSong < end)
359 360
			/* no-op, can't be moved to offset of itself */
			return PLAYLIST_RESULT_SUCCESS;
361
		to = (currentSong + abs(to)) % GetLength();
362 363
		if (start < (unsigned)to)
			to--;
364 365
	}

366
	queue.MoveRange(start, end, to);
367

368
	if (!queue.random) {
369
		/* update current/queued */
370 371 372 373 374 375
		if ((int)start <= current && (unsigned)current < end)
			current += to - start;
		else if (current >= (int)end && current <= to)
			current -= end - start;
		else if (current >= to && current < (int)start)
			current += end - start;
376 377
	}

378 379
	UpdateQueuedSong(pc, queued_song);
	OnModified();
380 381 382 383 384

	return PLAYLIST_RESULT_SUCCESS;
}

enum playlist_result
385
playlist::MoveId(player_control &pc, unsigned id1, int to)
386
{
387
	int song = queue.IdToPosition(id1);
388 389 390
	if (song < 0)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

391
	return MoveRange(pc, song, song + 1, to);
392 393
}

Max Kellermann's avatar
Max Kellermann committed
394
void
395
playlist::Shuffle(player_control &pc, unsigned start, unsigned end)
396
{
397
	if (end > GetLength())
398
		/* correct the "end" offset */
399
		end = GetLength();
400

401
	if (start + 1 >= end)
402
		/* needs at least two entries. */
403 404
		return;

405
	const Song *const queued_song = GetQueuedSong();
406 407
	if (playing && current >= 0) {
		unsigned current_position = queue.OrderToPosition(current);
408

409
		if (current_position >= start && current_position < end) {
410
			/* put current playing song first */
411
			queue.SwapPositions(start, current_position);
412

413 414
			if (queue.random) {
				current = queue.PositionToOrder(start);
415
			} else
416
				current = start;
417 418 419 420

			/* start shuffle after the current song */
			start++;
		}
421
	} else {
422
		/* no playback currently: reset current */
423

424
		current = -1;
425 426
	}

427
	queue.ShuffleRange(start, end);
428

429 430
	UpdateQueuedSong(pc, queued_song);
	OnModified();
431
}