PlaylistEdit.cxx 9.51 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 "PlayerControl.hxx"
Max Kellermann's avatar
Max Kellermann committed
29
#include "util/UriUtil.hxx"
30

31
extern "C" {
32
#include "song.h"
33
}
34

Max Kellermann's avatar
Max Kellermann committed
35
#include "Idle.hxx"
36 37 38
#include "DatabaseGlue.hxx"
#include "DatabasePlugin.hxx"

39 40
#include <stdlib.h>

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

	idle_add(IDLE_PLAYLIST);
}

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

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

57
	OnModified();
58 59 60
}

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

68
	return AppendSong(pc, song, added_id);
69 70 71
}

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

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

80
	const struct song *const queued_song = GetQueuedSong();
81

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

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

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

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

	if (added_id)
		*added_id = id;

	return PLAYLIST_RESULT_SUCCESS;
}

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

112 113 114 115 116 117 118 119 120 121 122 123 124
	const Database *db = nullptr;
	struct song *song;
	if (uri_has_scheme(uri)) {
		song = song_remote_new(uri);
	} else {
		db = GetDatabase(nullptr);
		if (db == nullptr)
			return PLAYLIST_RESULT_NO_SUCH_SONG;

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

126
	enum playlist_result result = AppendSong(pc, song, added_id);
127 128
	if (db != nullptr)
		db->ReturnSong(song);
129 130

	return result;
Max Kellermann's avatar
Max Kellermann committed
131 132 133
}

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

139
	const struct song *const queued_song = GetQueuedSong();
140

141
	queue.SwapPositions(song1, song2);
142

143 144
	if (queue.random) {
		/* update the queue order, so that current
145 146
		   still points to the current song order */

147 148
		queue.SwapOrders(queue.PositionToOrder(song1),
				 queue.PositionToOrder(song2));
149 150 151
	} else {
		/* correct the "current" song order */

152 153 154 155
		if (current == (int)song1)
			current = song2;
		else if (current == (int)song2)
			current = song1;
156 157
	}

158 159
	UpdateQueuedSong(pc, queued_song);
	OnModified();
160 161 162 163 164

	return PLAYLIST_RESULT_SUCCESS;
}

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

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

173
	return SwapPositions(pc, song1, song2);
174 175
}

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

184 185
	if (end > GetLength())
		end = GetLength();
186 187 188 189 190 191

	if (start >= end)
		return PLAYLIST_RESULT_SUCCESS;

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

192 193
	const int current_position = GetCurrentPosition();
	const struct song *const queued_song = GetQueuedSong();
194 195 196

	/* apply the priority changes */

197
	queue.SetPriorityRange(start, end, priority, current);
198 199 200 201

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

	if (current_position >= 0)
202
		current = queue.PositionToOrder(current_position);
203

204 205
	UpdateQueuedSong(pc, queued_song);
	OnModified();
206 207 208 209 210

	return PLAYLIST_RESULT_SUCCESS;
}

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

218 219
	return SetPriorityRange(pc, song_position, song_position + 1,
				priority);
220 221 222

}

223 224
void
playlist::DeleteInternal(player_control &pc,
225
			 unsigned song, const struct song **queued_p)
226
{
227
	assert(song < GetLength());
228

229
	unsigned songOrder = queue.PositionToOrder(song);
230

231
	if (playing && current == (int)songOrder) {
232
		const bool paused = pc.GetState() == PLAYER_STATE_PAUSE;
233 234 235

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

236
		pc.Stop();
237
		playing = false;
238 239 240

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

241 242 243
		current = queue.GetNextOrder(current);
		if (current == (int)songOrder)
			current = -1;
244

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

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

	/* now do it: remove the song */

261
	queue.DeletePosition(song);
262 263 264

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

265 266
	if (current > (int)songOrder)
		current--;
267 268 269
}

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

275
	const struct song *queued_song = GetQueuedSong();
276

277
	DeleteInternal(pc, song, &queued_song);
278

279 280
	UpdateQueuedSong(pc, queued_song);
	OnModified();
281 282 283 284

	return PLAYLIST_RESULT_SUCCESS;
}

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

291 292
	if (end > queue.GetLength())
		end = queue.GetLength();
293 294 295 296

	if (start >= end)
		return PLAYLIST_RESULT_SUCCESS;

297
	const struct song *queued_song = GetQueuedSong();
298 299

	do {
300
		DeleteInternal(pc, --end, &queued_song);
301 302
	} while (end != start);

303 304
	UpdateQueuedSong(pc, queued_song);
	OnModified();
305 306 307 308

	return PLAYLIST_RESULT_SUCCESS;
}

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

316
	return DeletePosition(pc, song);
317 318 319
}

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

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

334 335
	if ((to >= 0 && to + end - start - 1 >= GetLength()) ||
	    (to < 0 && unsigned(abs(to)) > GetLength()))
336 337
		return PLAYLIST_RESULT_BAD_RANGE;

338 339
	if ((int)start == to)
		/* nothing happens */
340 341
		return PLAYLIST_RESULT_SUCCESS;

342
	const struct song *const queued_song = GetQueuedSong();
343 344 345 346 347

	/*
	 * (to < 0) => move to offset from current song
	 * (-playlist.length == to) => move to position BEFORE current song
	 */
348 349
	const int currentSong = GetCurrentPosition();
	if (to < 0 && currentSong >= 0) {
350
		if (start <= (unsigned)currentSong && (unsigned)currentSong < end)
351 352
			/* no-op, can't be moved to offset of itself */
			return PLAYLIST_RESULT_SUCCESS;
353
		to = (currentSong + abs(to)) % GetLength();
354 355
		if (start < (unsigned)to)
			to--;
356 357
	}

358
	queue.MoveRange(start, end, to);
359

360
	if (!queue.random) {
361
		/* update current/queued */
362 363 364 365 366 367
		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;
368 369
	}

370 371
	UpdateQueuedSong(pc, queued_song);
	OnModified();
372 373 374 375 376

	return PLAYLIST_RESULT_SUCCESS;
}

enum playlist_result
377
playlist::MoveId(player_control &pc, unsigned id1, int to)
378
{
379
	int song = queue.IdToPosition(id1);
380 381 382
	if (song < 0)
		return PLAYLIST_RESULT_NO_SUCH_SONG;

383
	return MoveRange(pc, song, song + 1, to);
384 385
}

Max Kellermann's avatar
Max Kellermann committed
386
void
387
playlist::Shuffle(player_control &pc, unsigned start, unsigned end)
388
{
389
	if (end > GetLength())
390
		/* correct the "end" offset */
391
		end = GetLength();
392

393
	if (start + 1 >= end)
394
		/* needs at least two entries. */
395 396
		return;

397 398 399
	const struct song *const queued_song = GetQueuedSong();
	if (playing && current >= 0) {
		unsigned current_position = queue.OrderToPosition(current);
400

401
		if (current_position >= start && current_position < end) {
402
			/* put current playing song first */
403
			queue.SwapPositions(start, current_position);
404

405 406
			if (queue.random) {
				current = queue.PositionToOrder(start);
407
			} else
408
				current = start;
409 410 411 412

			/* start shuffle after the current song */
			start++;
		}
413
	} else {
414
		/* no playback currently: reset current */
415

416
		current = -1;
417 418
	}

419
	queue.ShuffleRange(start, end);
420

421 422
	UpdateQueuedSong(pc, queued_song);
	OnModified();
423
}