command.c 38.4 KB
Newer Older
Warren Dukes's avatar
Warren Dukes committed
1
/* the Music Player Daemon (MPD)
2
 * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com)
Warren Dukes's avatar
Warren Dukes committed
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * This project's homepage is: 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.
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "command.h"
20
#include "player_control.h"
Warren Dukes's avatar
Warren Dukes committed
21 22 23
#include "playlist.h"
#include "ls.h"
#include "directory.h"
24
#include "directory_print.h"
25
#include "database.h"
26
#include "update.h"
Warren Dukes's avatar
Warren Dukes committed
27 28 29
#include "volume.h"
#include "stats.h"
#include "permission.h"
30
#include "buffer2array.h"
31
#include "log.h"
32
#include "utils.h"
33
#include "stored_playlist.h"
Max Kellermann's avatar
Max Kellermann committed
34
#include "ack.h"
35
#include "audio.h"
Max Kellermann's avatar
Max Kellermann committed
36
#include "dbUtils.h"
Max Kellermann's avatar
Max Kellermann committed
37
#include "tag.h"
38
#include "client.h"
39
#include "tag_print.h"
40
#include "path.h"
41
#include "os_compat.h"
Warren Dukes's avatar
Warren Dukes committed
42 43 44 45 46 47 48 49

#define COMMAND_STATUS_VOLUME           "volume"
#define COMMAND_STATUS_STATE            "state"
#define COMMAND_STATUS_REPEAT           "repeat"
#define COMMAND_STATUS_RANDOM           "random"
#define COMMAND_STATUS_PLAYLIST         "playlist"
#define COMMAND_STATUS_PLAYLIST_LENGTH  "playlistlength"
#define COMMAND_STATUS_SONG             "song"
50
#define COMMAND_STATUS_SONGID           "songid"
Warren Dukes's avatar
Warren Dukes committed
51 52 53
#define COMMAND_STATUS_TIME             "time"
#define COMMAND_STATUS_BITRATE          "bitrate"
#define COMMAND_STATUS_ERROR            "error"
54 55
#define COMMAND_STATUS_CROSSFADE	"xfade"
#define COMMAND_STATUS_AUDIO		"audio"
56
#define COMMAND_STATUS_UPDATING_DB	"updating_db"
Warren Dukes's avatar
Warren Dukes committed
57

58 59 60 61 62 63 64
/*
 * The most we ever use is for search/find, and that limits it to the
 * number of tags we can have.  Add one for the command, and one extra
 * to catch errors clients may send us
 */
#define COMMAND_ARGV_MAX	(2+(TAG_NUM_OF_ITEM_TYPES*2))

Warren Dukes's avatar
Warren Dukes committed
65 66
/* if min: -1 don't check args *
 * if max: -1 no max args      */
67
struct command {
Max Kellermann's avatar
Max Kellermann committed
68
	const char *cmd;
Max Kellermann's avatar
Max Kellermann committed
69
	unsigned permission;
Avuton Olrich's avatar
Avuton Olrich committed
70 71
	int min;
	int max;
72
	enum command_return (*handler)(struct client *client, int argc, char **argv);
73 74
};

75 76 77 78 79 80 81
/* this should really be "need a non-negative integer": */
static const char need_positive[] = "need a positive integer"; /* no-op */

/* FIXME: redundant error messages */
static const char check_integer[] = "\"%s\" is not a integer";
static const char need_integer[] = "need an integer";

Max Kellermann's avatar
Max Kellermann committed
82
static const char *current_command;
Max Kellermann's avatar
Max Kellermann committed
83
static int command_list_num;
Warren Dukes's avatar
Warren Dukes committed
84

85 86 87 88 89
void command_success(struct client *client)
{
	client_puts(client, "OK\n");
}

90
static void command_error_v(struct client *client, enum ack error,
91 92 93 94 95 96
			    const char *fmt, va_list args)
{
	assert(client != NULL);
	assert(current_command != NULL);

	client_printf(client, "ACK [%i@%i] {%s} ",
Max Kellermann's avatar
Max Kellermann committed
97
		      (int)error, command_list_num, current_command);
98 99 100 101 102 103
	client_vprintf(client, fmt, args);
	client_puts(client, "\n");

	current_command = NULL;
}

104
mpd_fprintf_ void command_error(struct client *client, enum ack error,
105 106 107 108 109 110 111 112
				const char *fmt, ...)
{
	va_list args;
	va_start(args, fmt);
	command_error_v(client, error, fmt, args);
	va_end(args);
}

113 114 115
static bool mpd_fprintf__
check_uint32(struct client *client, uint32_t *dst,
	     const char *s, const char *fmt, ...)
116 117 118 119 120 121 122
{
	char *test;

	*dst = strtoul(s, &test, 10);
	if (*test != '\0') {
		va_list args;
		va_start(args, fmt);
123
		command_error_v(client, ACK_ERROR_ARG, fmt, args);
124
		va_end(args);
125
		return false;
126
	}
127
	return true;
128 129
}

130
static bool mpd_fprintf__
131
check_int(struct client *client, int *value_r,
132
	  const char *s, const char *fmt, ...)
133 134
{
	char *test;
135
	long value;
136

137
	value = strtol(s, &test, 10);
138
	if (*test != '\0') {
139 140
		va_list args;
		va_start(args, fmt);
141
		command_error_v(client, ACK_ERROR_ARG, fmt, args);
142
		va_end(args);
143
		return false;
144
	}
145 146 147 148 149 150 151 152 153 154

#if LONG_MAX > INT_MAX
	if (value < INT_MIN || value > INT_MAX) {
		command_error(client, ACK_ERROR_ARG,
			      "Number too large: %s", s);
		return false;
	}
#endif

	*value_r = (int)value;
155
	return true;
156 157
}

158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
static bool
check_unsigned(struct client *client, unsigned *value_r, const char *s)
{
	unsigned long value;
	char *endptr;

	value = strtoul(s, &endptr, 10);
	if (*endptr != 0) {
		command_error(client, ACK_ERROR_ARG,
			      "Integer expected: %s", s);
		return false;
	}

	if (value > UINT_MAX) {
		command_error(client, ACK_ERROR_ARG,
			      "Number too large: %s", s);
		return false;
	}

	*value_r = (unsigned)value;
	return true;
}

static bool
check_bool(struct client *client, bool *value_r, const char *s)
{
	long value;
	char *endptr;

	value = strtol(s, &endptr, 10);
188
	if (*endptr != 0 || (value != 0 && value != 1)) {
189 190 191 192 193 194 195 196 197
		command_error(client, ACK_ERROR_ARG,
			      "Boolean (0/1) expected: %s", s);
		return false;
	}

	*value_r = !!value;
	return true;
}

198 199 200
static enum command_return
print_playlist_result(struct client *client,
		      enum playlist_result result)
201 202 203
{
	switch (result) {
	case PLAYLIST_RESULT_SUCCESS:
204
		return COMMAND_RETURN_OK;
205 206

	case PLAYLIST_RESULT_ERRNO:
207
		command_error(client, ACK_ERROR_SYSTEM, strerror(errno));
208
		return COMMAND_RETURN_ERROR;
209

210 211
	case PLAYLIST_RESULT_DENIED:
		command_error(client, ACK_ERROR_NO_EXIST, "Access denied");
212
		return COMMAND_RETURN_ERROR;
213

214
	case PLAYLIST_RESULT_NO_SUCH_SONG:
215
		command_error(client, ACK_ERROR_NO_EXIST, "No such song");
216
		return COMMAND_RETURN_ERROR;
217 218

	case PLAYLIST_RESULT_NO_SUCH_LIST:
219
		command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
220
		return COMMAND_RETURN_ERROR;
221 222

	case PLAYLIST_RESULT_LIST_EXISTS:
223
		command_error(client, ACK_ERROR_EXIST,
224
			      "Playlist already exists");
225
		return COMMAND_RETURN_ERROR;
226 227

	case PLAYLIST_RESULT_BAD_NAME:
228 229 230 231
		command_error(client, ACK_ERROR_ARG,
			      "playlist name is invalid: "
			      "playlist names may not contain slashes,"
			      " newlines or carriage returns");
232
		return COMMAND_RETURN_ERROR;
233 234

	case PLAYLIST_RESULT_BAD_RANGE:
235
		command_error(client, ACK_ERROR_ARG, "Bad song index");
236
		return COMMAND_RETURN_ERROR;
237 238

	case PLAYLIST_RESULT_NOT_PLAYING:
239
		command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing");
240
		return COMMAND_RETURN_ERROR;
241 242

	case PLAYLIST_RESULT_TOO_LARGE:
243 244
		command_error(client, ACK_ERROR_PLAYLIST_MAX,
			      "playlist is at the max size");
245
		return COMMAND_RETURN_ERROR;
246 247 248
	}

	assert(0);
249
	return COMMAND_RETURN_ERROR;
250 251
}

252 253 254 255 256 257
static void
print_spl_list(struct client *client, GPtrArray *list)
{
	for (unsigned i = 0; i < list->len; ++i) {
		struct stored_playlist_info *playlist =
			g_ptr_array_index(list, i);
258 259 260
		time_t t;
		struct tm tm;
		char timestamp[32];
261 262

		client_printf(client, "playlist: %s\n", playlist->name);
263 264 265 266 267

		t = playlist->mtime;
		strftime(timestamp, sizeof(timestamp), "%FT%TZ",
			 gmtime_r(&t, &tm));
		client_printf(client, "Last-Modified: %s\n", timestamp);
268 269 270
	}
}

271
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
272 273
handle_urlhandlers(struct client *client,
		   mpd_unused int argc, mpd_unused char *argv[])
274
{
275 276
	if (client_get_uid(client) > 0)
		client_puts(client, "handler: file://\n");
277
	return printRemoteUrlHandlers(client);
278 279
}

280
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
281 282
handle_tagtypes(struct client *client,
		mpd_unused int argc, mpd_unused char *argv[])
283
{
284
	tag_print_types(client);
285
	return COMMAND_RETURN_OK;
286 287
}

288
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
289
handle_play(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
290
{
Avuton Olrich's avatar
Avuton Olrich committed
291
	int song = -1;
292
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
293

294
	if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
295
		return COMMAND_RETURN_ERROR;
296
	result = playPlaylist(song, 0);
297
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
298 299
}

300
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
301
handle_playid(struct client *client, int argc, char *argv[])
302
{
Avuton Olrich's avatar
Avuton Olrich committed
303
	int id = -1;
304
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
305

306
	if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
307
		return COMMAND_RETURN_ERROR;
308

309
	result = playPlaylistById(id, 0);
310
	return print_playlist_result(client, result);
311 312
}

313
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
314 315
handle_stop(mpd_unused struct client *client,
	    mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
316
{
317
	stopPlaylist();
318
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
319 320
}

321
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
322 323
handle_currentsong(struct client *client,
		   mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
324
{
Avuton Olrich's avatar
Avuton Olrich committed
325
	int song = getPlaylistCurrentSong();
326
	enum playlist_result result;
Warren Dukes's avatar
Warren Dukes committed
327

328
	if (song < 0)
329
		return COMMAND_RETURN_OK;
330

331
	result = playlistInfo(client, song);
332
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
333 334
}

335
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
336 337
handle_pause(struct client *client,
	     int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
338
{
339
	if (argc == 2) {
340 341
		bool pause_flag;
		if (!check_bool(client, &pause_flag, argv[1]))
342
			return COMMAND_RETURN_ERROR;
343
		playerSetPause(pause_flag);
344
		return COMMAND_RETURN_OK;
Avuton Olrich's avatar
Avuton Olrich committed
345
	}
346 347

	playerPause();
348
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
349 350
}

351
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
352 353
handle_status(struct client *client,
	      mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
354
{
Max Kellermann's avatar
Max Kellermann committed
355
	const char *state = NULL;
Warren Dukes's avatar
Warren Dukes committed
356
	int updateJobId;
Avuton Olrich's avatar
Avuton Olrich committed
357
	int song;
Warren Dukes's avatar
Warren Dukes committed
358

359
	playPlaylistIfPlayerStopped();
Avuton Olrich's avatar
Avuton Olrich committed
360 361
	switch (getPlayerState()) {
	case PLAYER_STATE_STOP:
362
		state = "stop";
Avuton Olrich's avatar
Avuton Olrich committed
363 364
		break;
	case PLAYER_STATE_PAUSE:
365
		state = "pause";
Avuton Olrich's avatar
Avuton Olrich committed
366 367
		break;
	case PLAYER_STATE_PLAY:
368
		state = "play";
Avuton Olrich's avatar
Avuton Olrich committed
369 370 371
		break;
	}

372
	client_printf(client,
373 374 375 376 377 378 379 380 381 382 383 384
		      COMMAND_STATUS_VOLUME ": %i\n"
		      COMMAND_STATUS_REPEAT ": %i\n"
		      COMMAND_STATUS_RANDOM ": %i\n"
		      COMMAND_STATUS_PLAYLIST ": %li\n"
		      COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
		      COMMAND_STATUS_CROSSFADE ": %i\n"
		      COMMAND_STATUS_STATE ": %s\n",
		      getVolumeLevel(),
		      getPlaylistRepeatStatus(),
		      getPlaylistRandomStatus(),
		      getPlaylistVersion(),
		      getPlaylistLength(),
385
		      (int)(getPlayerCrossFade() + 0.5),
386
		      state);
Avuton Olrich's avatar
Avuton Olrich committed
387 388 389

	song = getPlaylistCurrentSong();
	if (song >= 0) {
390 391
		client_printf(client,
			      COMMAND_STATUS_SONG ": %i\n"
392
			      COMMAND_STATUS_SONGID ": %u\n",
393
			      song, getPlaylistSongId(song));
Avuton Olrich's avatar
Avuton Olrich committed
394
	}
395

Avuton Olrich's avatar
Avuton Olrich committed
396
	if (getPlayerState() != PLAYER_STATE_STOP) {
397
		const struct audio_format *af = player_get_audio_format();
398
		client_printf(client,
399 400
			      COMMAND_STATUS_TIME ": %i:%i\n"
			      COMMAND_STATUS_BITRATE ": %li\n"
401
			      COMMAND_STATUS_AUDIO ": %u:%u:%u\n",
402
			      getPlayerElapsedTime(), getPlayerTotalTime(),
403
			      getPlayerBitRate(),
404
			      af->sample_rate, af->bits, af->channels);
405
	}
Avuton Olrich's avatar
Avuton Olrich committed
406 407

	if ((updateJobId = isUpdatingDB())) {
408 409 410
		client_printf(client,
			      COMMAND_STATUS_UPDATING_DB ": %i\n",
			      updateJobId);
Warren Dukes's avatar
Warren Dukes committed
411
	}
412

Avuton Olrich's avatar
Avuton Olrich committed
413
	if (getPlayerError() != PLAYER_ERROR_NOERROR) {
414 415 416
		client_printf(client,
			      COMMAND_STATUS_ERROR ": %s\n",
			      getPlayerErrorStr());
Avuton Olrich's avatar
Avuton Olrich committed
417
	}
Warren Dukes's avatar
Warren Dukes committed
418

419
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
420 421
}

422
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
423 424
handle_kill(mpd_unused struct client *client,
	    mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
425
{
Avuton Olrich's avatar
Avuton Olrich committed
426
	return COMMAND_RETURN_KILL;
Warren Dukes's avatar
Warren Dukes committed
427 428
}

429
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
430 431
handle_close(mpd_unused struct client *client,
	     mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
432
{
Avuton Olrich's avatar
Avuton Olrich committed
433
	return COMMAND_RETURN_CLOSE;
Warren Dukes's avatar
Warren Dukes committed
434 435
}

436
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
437
handle_add(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
438
{
439
	char *path = argv[1];
440
	enum playlist_result result;
441

442 443
	if (strncmp(path, "file:///", 8) == 0) {
		result = playlist_append_file(path + 7, client_get_uid(client),
444 445 446 447
					      NULL);
		return print_playlist_result(client, result);
	}

Avuton Olrich's avatar
Avuton Olrich committed
448
	if (isRemoteUrl(path))
449
		return addToPlaylist(path, NULL);
Warren Dukes's avatar
Warren Dukes committed
450

451 452
	result = addAllIn(path);
	if (result == (enum playlist_result)-1) {
453 454
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
455
		return COMMAND_RETURN_ERROR;
456 457
	}

458
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
459 460
}

461
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
462
handle_addid(struct client *client, int argc, char *argv[])
463
{
464
	unsigned added_id;
465 466
	enum playlist_result result;

467 468 469
	if (strncmp(argv[1], "file:///", 8) == 0)
		result = playlist_append_file(argv[1] + 7,
					      client_get_uid(client),
470 471 472
					      &added_id);
	else
		result = addToPlaylist(argv[1], &added_id);
473

474
	if (result != PLAYLIST_RESULT_SUCCESS)
475
		return print_playlist_result(client, result);
476 477 478

	if (argc == 3) {
		int to;
479
		if (!check_int(client, &to, argv[2], check_integer, argv[2]))
480
			return COMMAND_RETURN_ERROR;
481 482
		result = moveSongInPlaylistById(added_id, to);
		if (result != PLAYLIST_RESULT_SUCCESS) {
483 484
			enum command_return ret =
				print_playlist_result(client, result);
485 486
			deleteFromPlaylistById(added_id);
			return ret;
487 488
		}
	}
489

490
	client_printf(client, "Id: %u\n", added_id);
491
	return result;
492 493
}

494
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
495
handle_delete(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
496
{
Avuton Olrich's avatar
Avuton Olrich committed
497
	int song;
498
	enum playlist_result result;
Warren Dukes's avatar
Warren Dukes committed
499

500
	if (!check_int(client, &song, argv[1], need_positive))
501
		return COMMAND_RETURN_ERROR;
502 503

	result = deleteFromPlaylist(song);
504
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
505 506
}

507
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
508
handle_deleteid(struct client *client, mpd_unused int argc, char *argv[])
509
{
Avuton Olrich's avatar
Avuton Olrich committed
510
	int id;
511
	enum playlist_result result;
512

513
	if (!check_int(client, &id, argv[1], need_positive))
514
		return COMMAND_RETURN_ERROR;
515 516

	result = deleteFromPlaylistById(id);
517
	return print_playlist_result(client, result);
518 519
}

520
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
521 522
handle_playlist(struct client *client,
	       mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
523
{
524
	showPlaylist(client);
525
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
526 527
}

528
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
529 530
handle_shuffle(mpd_unused struct client *client,
	       mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
531
{
532
	shufflePlaylist();
533
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
534 535
}

536
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
537 538
handle_clear(mpd_unused struct client *client,
	     mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
539
{
540
	clearPlaylist();
541
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
542 543
}

544
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
545 546
handle_save(struct client *client,
	    mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
547
{
548 549 550
	enum playlist_result result;

	result = savePlaylist(argv[1]);
551
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
552 553
}

554
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
555
handle_load(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
556
{
557 558
	enum playlist_result result;

559
	result = loadPlaylist(client, argv[1]);
560
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
561
}
562

563
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
564
handle_listplaylist(struct client *client, mpd_unused int argc, char *argv[])
565
{
566 567
	int ret;

568
	ret = PlaylistInfo(client, argv[1], 0);
569
	if (ret == -1)
570
		command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
571 572

	return ret;
573
}
574

575
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
576 577
handle_listplaylistinfo(struct client *client,
			mpd_unused int argc, char *argv[])
578
{
579 580
	int ret;

581
	ret = PlaylistInfo(client, argv[1], 1);
582
	if (ret == -1)
583
		command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
584 585

	return ret;
586
}
Warren Dukes's avatar
Warren Dukes committed
587

588
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
589
handle_lsinfo(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
590
{
Max Kellermann's avatar
Max Kellermann committed
591
	const char *path = "";
592
	const struct directory *directory;
593 594 595 596

	if (argc == 2)
		path = argv[1];

597
	directory = db_get_directory(path);
598
	if (directory == NULL) {
599 600
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory not found");
601
		return COMMAND_RETURN_ERROR;
602
	}
603

604 605
	directory_print(client, directory);

606 607 608 609 610 611 612
	if (isRootDirectory(path)) {
		GPtrArray *list = spl_list();
		if (list != NULL) {
			print_spl_list(client, list);
			spl_list_free(list);
		}
	}
613

614
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
615 616
}

617
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
618
handle_rm(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
619
{
620 621
	enum playlist_result result;

622
	result = spl_delete(argv[1]);
623
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
624 625
}

626
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
627
handle_rename(struct client *client, mpd_unused int argc, char *argv[])
628
{
629 630
	enum playlist_result result;

631
	result = spl_rename(argv[1], argv[2]);
632
	return print_playlist_result(client, result);
633 634
}

635
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
636
handle_plchanges(struct client *client, mpd_unused int argc, char *argv[])
637
{
638
	uint32_t version;
639

640
	if (!check_uint32(client, &version, argv[1], need_positive))
641
		return COMMAND_RETURN_ERROR;
642
	return playlistChanges(client, version);
643 644
}

645
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
646
handle_plchangesposid(struct client *client, mpd_unused int argc, char *argv[])
647
{
648
	uint32_t version;
649

650
	if (!check_uint32(client, &version, argv[1], need_positive))
651
		return COMMAND_RETURN_ERROR;
652
	return playlistChangesPosId(client, version);
653 654
}

655
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
656
handle_playlistinfo(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
657
{
Avuton Olrich's avatar
Avuton Olrich committed
658
	int song = -1;
659
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
660

661
	if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
662
		return COMMAND_RETURN_ERROR;
663

664
	result = playlistInfo(client, song);
665
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
666 667
}

668
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
669
handle_playlistid(struct client *client, int argc, char *argv[])
670
{
Avuton Olrich's avatar
Avuton Olrich committed
671
	int id = -1;
672
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
673

674
	if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
675
		return COMMAND_RETURN_ERROR;
676

677
	result = playlistId(client, id);
678
	return print_playlist_result(client, result);
679 680
}

681
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
682
handle_find(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
683
{
684 685
	int ret;

Avuton Olrich's avatar
Avuton Olrich committed
686
	LocateTagItem *items;
687 688
	int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
							 argc - 1,
Avuton Olrich's avatar
Avuton Olrich committed
689
							 &items);
690

Avuton Olrich's avatar
Avuton Olrich committed
691
	if (numItems <= 0) {
692
		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
693
		return COMMAND_RETURN_ERROR;
694 695
	}

696
	ret = findSongsIn(client, NULL, numItems, items);
697
	if (ret == -1)
698 699
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
700

701
	freeLocateTagItemArray(numItems, items);
702 703

	return ret;
Warren Dukes's avatar
Warren Dukes committed
704 705
}

706
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
707
handle_search(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
708
{
709 710
	int ret;

Avuton Olrich's avatar
Avuton Olrich committed
711
	LocateTagItem *items;
712 713
	int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
							 argc - 1,
Avuton Olrich's avatar
Avuton Olrich committed
714
							 &items);
715

Avuton Olrich's avatar
Avuton Olrich committed
716
	if (numItems <= 0) {
717
		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
718
		return COMMAND_RETURN_ERROR;
719 720
	}

721
	ret = searchForSongsIn(client, NULL, numItems, items);
722
	if (ret == -1)
723 724
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
725

726
	freeLocateTagItemArray(numItems, items);
727 728

	return ret;
Warren Dukes's avatar
Warren Dukes committed
729 730
}

731
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
732
handle_count(struct client *client, int argc, char *argv[])
733 734 735 736 737 738 739 740 741
{
	int ret;

	LocateTagItem *items;
	int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
							 argc - 1,
							 &items);

	if (numItems <= 0) {
742
		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
743
		return COMMAND_RETURN_ERROR;
744 745
	}

746
	ret = searchStatsForSongsIn(client, NULL, numItems, items);
747
	if (ret == -1)
748 749
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
750 751 752 753 754 755

	freeLocateTagItemArray(numItems, items);

	return ret;
}

756
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
757
handle_playlistfind(struct client *client, int argc, char *argv[])
758 759 760 761 762 763 764
{
	LocateTagItem *items;
	int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
							 argc - 1,
							 &items);

	if (numItems <= 0) {
765
		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
766
		return COMMAND_RETURN_ERROR;
767 768
	}

769
	findSongsInPlaylist(client, numItems, items);
770 771 772

	freeLocateTagItemArray(numItems, items);

773
	return COMMAND_RETURN_OK;
774 775
}

776
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
777
handle_playlistsearch(struct client *client, int argc, char *argv[])
778 779 780 781 782 783 784
{
	LocateTagItem *items;
	int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
							 argc - 1,
							 &items);

	if (numItems <= 0) {
785
		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
786
		return COMMAND_RETURN_ERROR;
787 788
	}

789
	searchForSongsInPlaylist(client, numItems, items);
790 791 792

	freeLocateTagItemArray(numItems, items);

793
	return COMMAND_RETURN_OK;
794 795
}

796
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
797 798
handle_playlistdelete(struct client *client,
		      mpd_unused int argc, char *argv[]) {
799 800
	char *playlist = argv[1];
	int from;
801
	enum playlist_result result;
802

803
	if (!check_int(client, &from, argv[2], check_integer, argv[2]))
804
		return COMMAND_RETURN_ERROR;
805

806
	result = spl_remove_index(playlist, from);
807
	return print_playlist_result(client, result);
808 809
}

810
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
811
handle_playlistmove(struct client *client, mpd_unused int argc, char *argv[])
812 813 814
{
	char *playlist = argv[1];
	int from, to;
815
	enum playlist_result result;
816

817
	if (!check_int(client, &from, argv[2], check_integer, argv[2]))
818
		return COMMAND_RETURN_ERROR;
819
	if (!check_int(client, &to, argv[3], check_integer, argv[3]))
820
		return COMMAND_RETURN_ERROR;
821

822
	result = spl_move_index(playlist, from, to);
823
	return print_playlist_result(client, result);
824 825
}

826
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
827
handle_update(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
828
{
829
	char *path = NULL;
830
	unsigned ret;
831

832
	assert(argc <= 2);
833 834
	if (argc == 2)
		path = g_strdup(argv[1]);
835 836 837 838

	ret = directory_update_init(path);
	if (ret > 0) {
		client_printf(client, "updating_db: %i\n", ret);
839
		return COMMAND_RETURN_OK;
840 841 842
	} else {
		command_error(client, ACK_ERROR_UPDATE_ALREADY,
			      "already updating");
843
		return COMMAND_RETURN_ERROR;
844
	}
Warren Dukes's avatar
Warren Dukes committed
845 846
}

847
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
848 849
handle_next(mpd_unused struct client *client,
	    mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
850
{
851
	nextSongInPlaylist();
852
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
853 854
}

855
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
856 857
handle_previous(mpd_unused struct client *client,
		mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
858
{
859
	previousSongInPlaylist();
860
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
861 862
}

863
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
864
handle_listall(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
865
{
Avuton Olrich's avatar
Avuton Olrich committed
866
	char *directory = NULL;
867
	int ret;
Warren Dukes's avatar
Warren Dukes committed
868

869 870
	if (argc == 2)
		directory = argv[1];
871

872
	ret = printAllIn(client, directory);
873
	if (ret == -1)
874 875
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
876 877

	return ret;
Warren Dukes's avatar
Warren Dukes committed
878 879
}

880
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
881
handle_volume(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
882
{
883
	int change, ret;
Warren Dukes's avatar
Warren Dukes committed
884

885
	if (!check_int(client, &change, argv[1], need_integer))
886
		return COMMAND_RETURN_ERROR;
887 888 889

	ret = changeVolumeLevel(change, 1);
	if (ret == -1)
890 891
		command_error(client, ACK_ERROR_SYSTEM,
			      "problems setting volume");
892 893

	return ret;
Warren Dukes's avatar
Warren Dukes committed
894 895
}

896
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
897
handle_setvol(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
898
{
899
	int level, ret;
Warren Dukes's avatar
Warren Dukes committed
900

901
	if (!check_int(client, &level, argv[1], need_integer))
902
		return COMMAND_RETURN_ERROR;
903 904 905

	ret = changeVolumeLevel(level, 0);
	if (ret == -1)
906 907
		command_error(client, ACK_ERROR_SYSTEM,
			      "problems setting volume");
908 909

	return ret;
Warren Dukes's avatar
Warren Dukes committed
910 911
}

912
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
913
handle_repeat(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
914
{
Avuton Olrich's avatar
Avuton Olrich committed
915
	int status;
Warren Dukes's avatar
Warren Dukes committed
916

917
	if (!check_int(client, &status, argv[1], need_integer))
918
		return COMMAND_RETURN_ERROR;
919 920

	if (status != 0 && status != 1) {
921 922
		command_error(client, ACK_ERROR_ARG,
			      "\"%i\" is not 0 or 1", status);
923
		return COMMAND_RETURN_ERROR;
924 925 926
	}

	setPlaylistRepeatStatus(status);
927
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
928 929
}

930
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
931
handle_random(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
932
{
Avuton Olrich's avatar
Avuton Olrich committed
933
	int status;
Warren Dukes's avatar
Warren Dukes committed
934

935
	if (!check_int(client, &status, argv[1], need_integer))
936
		return COMMAND_RETURN_ERROR;
937 938

	if (status != 0 && status != 1) {
939 940
		command_error(client, ACK_ERROR_ARG,
			      "\"%i\" is not 0 or 1", status);
941
		return COMMAND_RETURN_ERROR;
942 943 944
	}

	setPlaylistRandomStatus(status);
945
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
946 947
}

948
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
949 950
handle_stats(struct client *client,
	     mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
951
{
952
	return printStats(client);
Warren Dukes's avatar
Warren Dukes committed
953 954
}

955
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
956 957
handle_clearerror(mpd_unused struct client *client,
		  mpd_unused int argc, mpd_unused char *argv[])
Warren Dukes's avatar
Warren Dukes committed
958
{
Avuton Olrich's avatar
Avuton Olrich committed
959
	clearPlayerError();
960
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
961 962
}

963
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
964
handle_list(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
965
{
966
	int numConditionals;
Avuton Olrich's avatar
Avuton Olrich committed
967
	LocateTagItem *conditionals = NULL;
968
	int tagType = getLocateTagItemType(argv[1]);
969 970
	int ret;

Avuton Olrich's avatar
Avuton Olrich committed
971
	if (tagType < 0) {
972
		command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]);
973
		return COMMAND_RETURN_ERROR;
974 975
	}

976
	if (tagType == LOCATE_TAG_ANY_TYPE) {
977 978
		command_error(client, ACK_ERROR_ARG,
			      "\"any\" is not a valid return tag type");
979
		return COMMAND_RETURN_ERROR;
980 981
	}

982
	/* for compatibility with < 0.12.0 */
983
	if (argc == 3) {
Avuton Olrich's avatar
Avuton Olrich committed
984
		if (tagType != TAG_ITEM_ALBUM) {
985 986 987
			command_error(client, ACK_ERROR_ARG,
				      "should be \"%s\" for 3 arguments",
				      mpdTagItemKeys[TAG_ITEM_ALBUM]);
988
			return COMMAND_RETURN_ERROR;
989 990
		}
		conditionals = newLocateTagItem(mpdTagItemKeys[TAG_ITEM_ARTIST],
991
						argv[2]);
992
		numConditionals = 1;
Avuton Olrich's avatar
Avuton Olrich committed
993 994
	} else {
		numConditionals =
995
		    newLocateTagItemArrayFromArgArray(argv + 2,
996
						      argc - 2, &conditionals);
Avuton Olrich's avatar
Avuton Olrich committed
997 998

		if (numConditionals < 0) {
999 1000
			command_error(client, ACK_ERROR_ARG,
				      "not able to parse args");
1001
			return COMMAND_RETURN_ERROR;
1002 1003
		}
	}
1004

1005
	ret = listAllUniqueTags(client, tagType, numConditionals, conditionals);
1006

Avuton Olrich's avatar
Avuton Olrich committed
1007 1008
	if (conditionals)
		freeLocateTagItemArray(numConditionals, conditionals);
Warren Dukes's avatar
Warren Dukes committed
1009

1010
	if (ret == -1)
1011 1012
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
1013

1014
	return ret;
Warren Dukes's avatar
Warren Dukes committed
1015 1016
}

1017
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1018
handle_move(struct client *client, mpd_unused int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1019
{
1020
	int from, to;
1021
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1022

1023
	if (!check_int(client, &from, argv[1], check_integer, argv[1]))
1024
		return COMMAND_RETURN_ERROR;
1025
	if (!check_int(client, &to, argv[2], check_integer, argv[2]))
1026
		return COMMAND_RETURN_ERROR;
1027
	result = moveSongInPlaylist(from, to);
1028
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1029 1030
}

1031
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1032
handle_moveid(struct client *client, mpd_unused int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1033
{
1034
	int id, to;
1035
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1036

1037
	if (!check_int(client, &id, argv[1], check_integer, argv[1]))
1038
		return COMMAND_RETURN_ERROR;
1039
	if (!check_int(client, &to, argv[2], check_integer, argv[2]))
1040
		return COMMAND_RETURN_ERROR;
1041
	result = moveSongInPlaylistById(id, to);
1042
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1043 1044
}

1045
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1046
handle_swap(struct client *client, mpd_unused int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1047
{
1048
	int song1, song2;
1049
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1050

1051
	if (!check_int(client, &song1, argv[1], check_integer, argv[1]))
1052
		return COMMAND_RETURN_ERROR;
1053
	if (!check_int(client, &song2, argv[2], check_integer, argv[2]))
1054
		return COMMAND_RETURN_ERROR;
1055
	result = swapSongsInPlaylist(song1, song2);
1056
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1057 1058
}

1059
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1060
handle_swapid(struct client *client, mpd_unused int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1061
{
1062
	int id1, id2;
1063
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1064

1065
	if (!check_int(client, &id1, argv[1], check_integer, argv[1]))
1066
		return COMMAND_RETURN_ERROR;
1067
	if (!check_int(client, &id2, argv[2], check_integer, argv[2]))
1068
		return COMMAND_RETURN_ERROR;
1069
	result = swapSongsInPlaylistById(id1, id2);
1070
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1071 1072
}

1073
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1074
handle_seek(struct client *client, mpd_unused int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1075
{
1076
	int song, seek_time;
1077
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1078

1079
	if (!check_int(client, &song, argv[1], check_integer, argv[1]))
1080
		return COMMAND_RETURN_ERROR;
1081
	if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
1082
		return COMMAND_RETURN_ERROR;
1083 1084

	result = seekSongInPlaylist(song, seek_time);
1085
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1086 1087
}

1088
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1089
handle_seekid(struct client *client, mpd_unused int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1090
{
1091
	int id, seek_time;
1092
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1093

1094
	if (!check_int(client, &id, argv[1], check_integer, argv[1]))
1095
		return COMMAND_RETURN_ERROR;
1096
	if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
1097
		return COMMAND_RETURN_ERROR;
1098 1099

	result = seekSongInPlaylistById(id, seek_time);
1100
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1101 1102
}

1103
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1104
handle_listallinfo(struct client *client, mpd_unused int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1105 1106
{
	char *directory = NULL;
1107
	int ret;
Avuton Olrich's avatar
Avuton Olrich committed
1108

1109 1110
	if (argc == 2)
		directory = argv[1];
1111

1112
	ret = printInfoForAllIn(client, directory);
1113
	if (ret == -1)
1114 1115
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
1116 1117

	return ret;
Avuton Olrich's avatar
Avuton Olrich committed
1118 1119
}

1120
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1121 1122
handle_ping(mpd_unused struct client *client,
	    mpd_unused int argc, mpd_unused char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1123
{
1124
	return COMMAND_RETURN_OK;
Avuton Olrich's avatar
Avuton Olrich committed
1125 1126
}

1127
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1128
handle_password(struct client *client, mpd_unused int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1129
{
1130
	unsigned permission = 0;
1131 1132

	if (getPermissionFromPassword(argv[1], &permission) < 0) {
1133
		command_error(client, ACK_ERROR_PASSWORD, "incorrect password");
1134
		return COMMAND_RETURN_ERROR;
Warren Dukes's avatar
Warren Dukes committed
1135 1136
	}

1137 1138
	client_set_permission(client, permission);

1139
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
1140 1141
}

1142
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1143
handle_crossfade(struct client *client, mpd_unused int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
1144
{
1145
	unsigned xfade_time;
Warren Dukes's avatar
Warren Dukes committed
1146

1147
	if (!check_unsigned(client, &xfade_time, argv[1]))
1148
		return COMMAND_RETURN_ERROR;
Max Kellermann's avatar
Max Kellermann committed
1149
	setPlayerCrossFade(xfade_time);
Warren Dukes's avatar
Warren Dukes committed
1150

1151
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
1152 1153
}

1154
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1155
handle_enableoutput(struct client *client, mpd_unused int argc, char *argv[])
1156
{
1157 1158
	unsigned device;
	int ret;
1159

1160
	if (!check_unsigned(client, &device, argv[1]))
1161
		return COMMAND_RETURN_ERROR;
1162 1163 1164

	ret = enableAudioDevice(device);
	if (ret == -1)
1165 1166
		command_error(client, ACK_ERROR_NO_EXIST,
			      "No such audio output");
1167 1168

	return ret;
1169 1170
}

1171
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1172
handle_disableoutput(struct client *client, mpd_unused int argc, char *argv[])
1173
{
1174 1175
	unsigned device;
	int ret;
1176

1177
	if (!check_unsigned(client, &device, argv[1]))
1178
		return COMMAND_RETURN_ERROR;
1179 1180 1181

	ret = disableAudioDevice(device);
	if (ret == -1)
1182 1183
		command_error(client, ACK_ERROR_NO_EXIST,
			      "No such audio output");
1184 1185

	return ret;
1186 1187
}

1188
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1189 1190
handle_devices(struct client *client,
	       mpd_unused int argc, mpd_unused char *argv[])
1191
{
1192
	printAudioDevices(client);
1193

1194
	return COMMAND_RETURN_OK;
1195 1196
}

1197
/* don't be fooled, this is the command handler for "commands" command */
1198
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1199 1200
handle_commands(struct client *client,
		mpd_unused int argc, mpd_unused char *argv[]);
1201

1202
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1203 1204
handle_not_commands(struct client *client,
		    mpd_unused int argc, mpd_unused char *argv[]);
1205

1206
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1207
handle_playlistclear(struct client *client, mpd_unused int argc, char *argv[])
1208
{
1209 1210
	enum playlist_result result;

1211
	result = spl_clear(argv[1]);
1212
	return print_playlist_result(client, result);
1213 1214
}

1215
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1216
handle_playlistadd(struct client *client, mpd_unused int argc, char *argv[])
1217 1218 1219
{
	char *playlist = argv[1];
	char *path = argv[2];
1220
	enum playlist_result result;
1221 1222

	if (isRemoteUrl(path))
1223
		result = spl_append_uri(path, playlist);
1224
	else
1225 1226 1227
		result = addAllInToStoredPlaylist(path, playlist);

	if (result == (enum playlist_result)-1) {
1228 1229
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
1230
		return COMMAND_RETURN_ERROR;
1231 1232
	}

1233
	return print_playlist_result(client, result);
1234 1235
}

1236
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1237 1238
handle_listplaylists(struct client *client,
		     mpd_unused int argc, mpd_unused char *argv[])
1239 1240 1241 1242 1243
{
	GPtrArray *list = spl_list();
	if (list == NULL) {
		command_error(client, ACK_ERROR_SYSTEM,
			      "failed to get list of stored playlists");
1244
		return COMMAND_RETURN_ERROR;
1245 1246 1247 1248
	}

	print_spl_list(client, list);
	spl_list_free(list);
1249
	return COMMAND_RETURN_OK;
1250 1251
}

1252
static enum command_return
1253 1254 1255 1256 1257 1258 1259 1260 1261 1262
handle_idle(struct client *client,
	    mpd_unused int argc, mpd_unused char *argv[])
{
	/* enable "idle" mode on this client */
	client_idle_wait(client);

	/* return value is "1" so the caller won't print "OK" */
	return 1;
}

1263 1264 1265 1266 1267 1268
/**
 * The command registry.
 *
 * This array must be sorted!
 */
static const struct command commands[] = {
Max Kellermann's avatar
Max Kellermann committed
1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282
	{ "add", PERMISSION_ADD, 1, 1, handle_add },
	{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
	{ "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
	{ "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
	{ "close", PERMISSION_NONE, -1, -1, handle_close },
	{ "commands", PERMISSION_NONE, 0, 0, handle_commands },
	{ "count", PERMISSION_READ, 2, -1, handle_count },
	{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
	{ "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
	{ "delete", PERMISSION_CONTROL, 1, 1, handle_delete },
	{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
	{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
	{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
	{ "find", PERMISSION_READ, 2, -1, handle_find },
1283
	{ "idle", PERMISSION_READ, 0, 0, handle_idle },
Max Kellermann's avatar
Max Kellermann committed
1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333
	{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
	{ "list", PERMISSION_READ, 1, -1, handle_list },
	{ "listall", PERMISSION_READ, 0, 1, handle_listall },
	{ "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo },
	{ "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist },
	{ "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo },
	{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
	{ "load", PERMISSION_ADD, 1, 1, handle_load },
	{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
	{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
	{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
	{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
	{ "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands },
	{ "outputs", PERMISSION_READ, 0, 0, handle_devices },
	{ "password", PERMISSION_NONE, 1, 1, handle_password },
	{ "pause", PERMISSION_CONTROL, 0, 1, handle_pause },
	{ "ping", PERMISSION_NONE, 0, 0, handle_ping },
	{ "play", PERMISSION_CONTROL, 0, 1, handle_play },
	{ "playid", PERMISSION_CONTROL, 0, 1, handle_playid },
	{ "playlist", PERMISSION_READ, 0, 0, handle_playlist },
	{ "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd },
	{ "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear },
	{ "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete },
	{ "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind },
	{ "playlistid", PERMISSION_READ, 0, 1, handle_playlistid },
	{ "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo },
	{ "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove },
	{ "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch },
	{ "plchanges", PERMISSION_READ, 1, 1, handle_plchanges },
	{ "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid },
	{ "previous", PERMISSION_CONTROL, 0, 0, handle_previous },
	{ "random", PERMISSION_CONTROL, 1, 1, handle_random },
	{ "rename", PERMISSION_CONTROL, 2, 2, handle_rename },
	{ "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat },
	{ "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
	{ "save", PERMISSION_CONTROL, 1, 1, handle_save },
	{ "search", PERMISSION_READ, 2, -1, handle_search },
	{ "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
	{ "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
	{ "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol },
	{ "shuffle", PERMISSION_CONTROL, 0, 0, handle_shuffle },
	{ "stats", PERMISSION_READ, 0, 0, handle_stats },
	{ "status", PERMISSION_READ, 0, 0, handle_status },
	{ "stop", PERMISSION_CONTROL, 0, 0, handle_stop },
	{ "swap", PERMISSION_CONTROL, 2, 2, handle_swap },
	{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
	{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
	{ "update", PERMISSION_ADMIN, 0, 1, handle_update },
	{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
	{ "volume", PERMISSION_CONTROL, 1, 1, handle_volume },
1334 1335 1336 1337 1338
};

static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);

/* don't be fooled, this is the command handler for "commands" command */
1339
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1340 1341
handle_commands(struct client *client,
		mpd_unused int argc, mpd_unused char *argv[])
1342 1343 1344 1345 1346 1347 1348
{
	const unsigned permission = client_get_permission(client);
	const struct command *cmd;

	for (unsigned i = 0; i < num_commands; ++i) {
		cmd = &commands[i];

Max Kellermann's avatar
Max Kellermann committed
1349
		if (cmd->permission == (permission & cmd->permission))
1350 1351 1352
			client_printf(client, "command: %s\n", cmd->cmd);
	}

1353
	return COMMAND_RETURN_OK;
1354 1355
}

1356
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1357 1358
handle_not_commands(struct client *client,
		    mpd_unused int argc, mpd_unused char *argv[])
1359 1360 1361 1362 1363 1364 1365
{
	const unsigned permission = client_get_permission(client);
	const struct command *cmd;

	for (unsigned i = 0; i < num_commands; ++i) {
		cmd = &commands[i];

Max Kellermann's avatar
Max Kellermann committed
1366
		if (cmd->permission != (permission & cmd->permission))
1367 1368 1369
			client_printf(client, "command: %s\n", cmd->cmd);
	}

1370
	return COMMAND_RETURN_OK;
1371 1372
}

Max Kellermann's avatar
Max Kellermann committed
1373
void command_init(void)
Avuton Olrich's avatar
Avuton Olrich committed
1374
{
1375 1376 1377 1378 1379
#ifndef NDEBUG
	/* ensure that the command list is sorted */
	for (unsigned i = 0; i < num_commands - 1; ++i)
		assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0);
#endif
Avuton Olrich's avatar
Avuton Olrich committed
1380 1381
}

Max Kellermann's avatar
Max Kellermann committed
1382
void command_finish(void)
Avuton Olrich's avatar
Avuton Olrich committed
1383
{
1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405
}

static const struct command *
command_lookup(const char *name)
{
	unsigned a = 0, b = num_commands, i;
	int cmp;

	/* binary search */
	do {
		i = (a + b) / 2;

		cmp = strcmp(name, commands[i].cmd);
		if (cmp == 0)
			return &commands[i];
		else if (cmp < 0)
			b = i;
		else if (cmp > 0)
			a = i + 1;
	} while (a < b);

	return NULL;
Avuton Olrich's avatar
Avuton Olrich committed
1406 1407
}

1408
static bool
Max Kellermann's avatar
Max Kellermann committed
1409 1410
command_check_request(const struct command *cmd, struct client *client,
		      unsigned permission, int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1411 1412 1413
{
	int min = cmd->min + 1;
	int max = cmd->max + 1;
Warren Dukes's avatar
Warren Dukes committed
1414

Max Kellermann's avatar
Max Kellermann committed
1415
	if (cmd->permission != (permission & cmd->permission)) {
1416 1417 1418 1419
		if (client != NULL)
			command_error(client, ACK_ERROR_PERMISSION,
				      "you don't have permission for \"%s\"",
				      cmd->cmd);
1420
		return false;
Warren Dukes's avatar
Warren Dukes committed
1421 1422
	}

Avuton Olrich's avatar
Avuton Olrich committed
1423
	if (min == 0)
1424
		return true;
Warren Dukes's avatar
Warren Dukes committed
1425

Avuton Olrich's avatar
Avuton Olrich committed
1426
	if (min == max && max != argc) {
1427 1428 1429 1430
		if (client != NULL)
			command_error(client, ACK_ERROR_ARG,
				      "wrong number of arguments for \"%s\"",
				      argv[0]);
1431
		return false;
Avuton Olrich's avatar
Avuton Olrich committed
1432
	} else if (argc < min) {
1433 1434 1435
		if (client != NULL)
			command_error(client, ACK_ERROR_ARG,
				      "too few arguments for \"%s\"", argv[0]);
1436
		return false;
Avuton Olrich's avatar
Avuton Olrich committed
1437
	} else if (argc > max && max /* != 0 */ ) {
1438 1439 1440
		if (client != NULL)
			command_error(client, ACK_ERROR_ARG,
				      "too many arguments for \"%s\"", argv[0]);
1441
		return false;
Avuton Olrich's avatar
Avuton Olrich committed
1442
	} else
1443
		return true;
Warren Dukes's avatar
Warren Dukes committed
1444 1445
}

1446
static const struct command *
Max Kellermann's avatar
Max Kellermann committed
1447 1448
command_checked_lookup(struct client *client, unsigned permission,
		       int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
1449
{
Warren Dukes's avatar
Warren Dukes committed
1450
	static char unknown[] = "";
1451
	const struct command *cmd;
Warren Dukes's avatar
Warren Dukes committed
1452

Warren Dukes's avatar
Warren Dukes committed
1453 1454
	current_command = unknown;

1455
	if (argc == 0)
Avuton Olrich's avatar
Avuton Olrich committed
1456
		return NULL;
Warren Dukes's avatar
Warren Dukes committed
1457

1458 1459
	cmd = command_lookup(argv[0]);
	if (cmd == NULL) {
1460 1461 1462
		if (client != NULL)
			command_error(client, ACK_ERROR_UNKNOWN,
				      "unknown command \"%s\"", argv[0]);
Avuton Olrich's avatar
Avuton Olrich committed
1463 1464
		return NULL;
	}
Warren Dukes's avatar
Warren Dukes committed
1465

Warren Dukes's avatar
Warren Dukes committed
1466 1467
	current_command = cmd->cmd;

1468
	if (!command_check_request(cmd, client, permission, argc, argv))
1469 1470 1471 1472 1473
		return NULL;

	return cmd;
}

1474
enum command_return
Max Kellermann's avatar
Max Kellermann committed
1475
command_process(struct client *client, char *commandString)
1476
{
1477 1478
	int argc;
	char *argv[COMMAND_ARGV_MAX] = { NULL };
1479
	const struct command *cmd;
1480
	enum command_return ret = COMMAND_RETURN_ERROR;
1481

Eric Wong's avatar
Eric Wong committed
1482
	if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX)))
1483
		return COMMAND_RETURN_OK;
1484

Max Kellermann's avatar
Max Kellermann committed
1485 1486
	cmd = command_checked_lookup(client, client_get_permission(client),
				     argc, argv);
Eric Wong's avatar
Eric Wong committed
1487 1488
	if (cmd)
		ret = cmd->handler(client, argc, argv);
Warren Dukes's avatar
Warren Dukes committed
1489

Warren Dukes's avatar
Warren Dukes committed
1490 1491
	current_command = NULL;

1492 1493 1494
	return ret;
}

1495
enum command_return
Max Kellermann's avatar
Max Kellermann committed
1496
command_process_list(struct client *client,
1497
		     bool list_ok, GSList *list)
1498
{
1499
	enum command_return ret = COMMAND_RETURN_OK;
1500

Max Kellermann's avatar
Max Kellermann committed
1501
	command_list_num = 0;
Warren Dukes's avatar
Warren Dukes committed
1502

1503 1504 1505
	for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
		char *cmd = cur->data;

Max Kellermann's avatar
Max Kellermann committed
1506
		DEBUG("command_process_list: process command \"%s\"\n",
1507 1508
		      cmd);
		ret = command_process(client, cmd);
Max Kellermann's avatar
Max Kellermann committed
1509
		DEBUG("command_process_list: command returned %i\n", ret);
1510
		if (ret != COMMAND_RETURN_OK || client_is_expired(client))
1511
			break;
Max Kellermann's avatar
Max Kellermann committed
1512
		else if (list_ok)
1513
			client_puts(client, "list_OK\n");
Max Kellermann's avatar
Max Kellermann committed
1514
		command_list_num++;
1515
	}
1516

Max Kellermann's avatar
Max Kellermann committed
1517
	command_list_num = 0;
1518
	return ret;
Warren Dukes's avatar
Warren Dukes committed
1519
}