command.c 39.7 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 "idle.h"
Warren Dukes's avatar
Warren Dukes committed
42

Max Kellermann's avatar
Max Kellermann committed
43 44 45
#include <assert.h>
#include <time.h>

Warren Dukes's avatar
Warren Dukes committed
46 47 48 49 50 51 52
#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"
53
#define COMMAND_STATUS_SONGID           "songid"
Warren Dukes's avatar
Warren Dukes committed
54 55 56
#define COMMAND_STATUS_TIME             "time"
#define COMMAND_STATUS_BITRATE          "bitrate"
#define COMMAND_STATUS_ERROR            "error"
57 58
#define COMMAND_STATUS_CROSSFADE	"xfade"
#define COMMAND_STATUS_AUDIO		"audio"
59
#define COMMAND_STATUS_UPDATING_DB	"updating_db"
Warren Dukes's avatar
Warren Dukes committed
60

61 62 63 64 65 66 67
/*
 * 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
68 69
/* if min: -1 don't check args *
 * if max: -1 no max args      */
70
struct command {
Max Kellermann's avatar
Max Kellermann committed
71
	const char *cmd;
Max Kellermann's avatar
Max Kellermann committed
72
	unsigned permission;
Avuton Olrich's avatar
Avuton Olrich committed
73 74
	int min;
	int max;
75
	enum command_return (*handler)(struct client *client, int argc, char **argv);
76 77
};

78 79 80 81 82 83 84
/* 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
85
static const char *current_command;
Max Kellermann's avatar
Max Kellermann committed
86
static int command_list_num;
Warren Dukes's avatar
Warren Dukes committed
87

88 89 90 91 92
void command_success(struct client *client)
{
	client_puts(client, "OK\n");
}

93
static void command_error_v(struct client *client, enum ack error,
94 95 96 97 98 99
			    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
100
		      (int)error, command_list_num, current_command);
101 102 103 104 105 106
	client_vprintf(client, fmt, args);
	client_puts(client, "\n");

	current_command = NULL;
}

107 108
G_GNUC_PRINTF(3, 4) void command_error(struct client *client, enum ack error,
				       const char *fmt, ...)
109 110 111 112 113 114 115
{
	va_list args;
	va_start(args, fmt);
	command_error_v(client, error, fmt, args);
	va_end(args);
}

116
static bool G_GNUC_PRINTF(4, 5)
117 118
check_uint32(struct client *client, uint32_t *dst,
	     const char *s, const char *fmt, ...)
119 120 121 122 123 124 125
{
	char *test;

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

133
static bool G_GNUC_PRINTF(4, 5)
134
check_int(struct client *client, int *value_r,
135
	  const char *s, const char *fmt, ...)
136 137
{
	char *test;
138
	long value;
139

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

#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;
158
	return true;
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 188 189 190
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);
191
	if (*endptr != 0 || (value != 0 && value != 1)) {
192 193 194 195 196 197 198 199 200
		command_error(client, ACK_ERROR_ARG,
			      "Boolean (0/1) expected: %s", s);
		return false;
	}

	*value_r = !!value;
	return true;
}

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

	case PLAYLIST_RESULT_ERRNO:
210
		command_error(client, ACK_ERROR_SYSTEM, "%s", strerror(errno));
211
		return COMMAND_RETURN_ERROR;
212

213 214
	case PLAYLIST_RESULT_DENIED:
		command_error(client, ACK_ERROR_NO_EXIST, "Access denied");
215
		return COMMAND_RETURN_ERROR;
216

217
	case PLAYLIST_RESULT_NO_SUCH_SONG:
218
		command_error(client, ACK_ERROR_NO_EXIST, "No such song");
219
		return COMMAND_RETURN_ERROR;
220 221

	case PLAYLIST_RESULT_NO_SUCH_LIST:
222
		command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
223
		return COMMAND_RETURN_ERROR;
224 225

	case PLAYLIST_RESULT_LIST_EXISTS:
226
		command_error(client, ACK_ERROR_EXIST,
227
			      "Playlist already exists");
228
		return COMMAND_RETURN_ERROR;
229 230

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

	case PLAYLIST_RESULT_BAD_RANGE:
238
		command_error(client, ACK_ERROR_ARG, "Bad song index");
239
		return COMMAND_RETURN_ERROR;
240 241

	case PLAYLIST_RESULT_NOT_PLAYING:
242
		command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing");
243
		return COMMAND_RETURN_ERROR;
244 245

	case PLAYLIST_RESULT_TOO_LARGE:
246 247
		command_error(client, ACK_ERROR_PLAYLIST_MAX,
			      "playlist is at the max size");
248
		return COMMAND_RETURN_ERROR;
249 250 251
	}

	assert(0);
252
	return COMMAND_RETURN_ERROR;
253 254
}

255 256 257 258 259 260
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);
261
		time_t t;
262
#ifndef WIN32
263
		struct tm tm;
264
#endif
265
		char timestamp[32];
266 267

		client_printf(client, "playlist: %s\n", playlist->name);
268 269 270

		t = playlist->mtime;
		strftime(timestamp, sizeof(timestamp), "%FT%TZ",
271 272 273 274 275 276
#ifdef WIN32
			 gmtime(&t)
#else
			 gmtime_r(&t, &tm)
#endif
			 );
277
		client_printf(client, "Last-Modified: %s\n", timestamp);
278 279 280
	}
}

281
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
282
handle_urlhandlers(struct client *client,
283
		   G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
284
{
285 286
	if (client_get_uid(client) > 0)
		client_puts(client, "handler: file://\n");
287 288
	printRemoteUrlHandlers(client);
	return COMMAND_RETURN_OK;
289 290
}

291
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
292
handle_tagtypes(struct client *client,
293
		G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
294
{
295
	tag_print_types(client);
296
	return COMMAND_RETURN_OK;
297 298
}

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

305
	if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
306
		return COMMAND_RETURN_ERROR;
307
	result = playPlaylist(song, 0);
308
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
309 310
}

311
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
312
handle_playid(struct client *client, int argc, char *argv[])
313
{
Avuton Olrich's avatar
Avuton Olrich committed
314
	int id = -1;
315
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
316

317
	if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
318
		return COMMAND_RETURN_ERROR;
319

320
	result = playPlaylistById(id, 0);
321
	return print_playlist_result(client, result);
322 323
}

324
static enum command_return
325 326
handle_stop(G_GNUC_UNUSED struct client *client,
	    G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
327
{
328
	stopPlaylist();
329
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
330 331
}

332
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
333
handle_currentsong(struct client *client,
334
		   G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
335
{
Avuton Olrich's avatar
Avuton Olrich committed
336
	int song = getPlaylistCurrentSong();
337
	enum playlist_result result;
Warren Dukes's avatar
Warren Dukes committed
338

339
	if (song < 0)
340
		return COMMAND_RETURN_OK;
341

342
	result = playlistInfo(client, song);
343
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
344 345
}

346
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
347 348
handle_pause(struct client *client,
	     int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
349
{
350
	if (argc == 2) {
351 352
		bool pause_flag;
		if (!check_bool(client, &pause_flag, argv[1]))
353
			return COMMAND_RETURN_ERROR;
354
		playerSetPause(pause_flag);
355
		return COMMAND_RETURN_OK;
Avuton Olrich's avatar
Avuton Olrich committed
356
	}
357 358

	playerPause();
359
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
360 361
}

362
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
363
handle_status(struct client *client,
364
	      G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
365
{
Max Kellermann's avatar
Max Kellermann committed
366
	const char *state = NULL;
Warren Dukes's avatar
Warren Dukes committed
367
	int updateJobId;
Avuton Olrich's avatar
Avuton Olrich committed
368
	int song;
Warren Dukes's avatar
Warren Dukes committed
369

370
	playPlaylistIfPlayerStopped();
Avuton Olrich's avatar
Avuton Olrich committed
371 372
	switch (getPlayerState()) {
	case PLAYER_STATE_STOP:
373
		state = "stop";
Avuton Olrich's avatar
Avuton Olrich committed
374 375
		break;
	case PLAYER_STATE_PAUSE:
376
		state = "pause";
Avuton Olrich's avatar
Avuton Olrich committed
377 378
		break;
	case PLAYER_STATE_PLAY:
379
		state = "play";
Avuton Olrich's avatar
Avuton Olrich committed
380 381 382
		break;
	}

383
	client_printf(client,
384 385 386 387 388 389 390
		      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",
391
		      volume_level_get(),
392 393 394 395
		      getPlaylistRepeatStatus(),
		      getPlaylistRandomStatus(),
		      getPlaylistVersion(),
		      getPlaylistLength(),
396
		      (int)(getPlayerCrossFade() + 0.5),
397
		      state);
Avuton Olrich's avatar
Avuton Olrich committed
398 399 400

	song = getPlaylistCurrentSong();
	if (song >= 0) {
401 402
		client_printf(client,
			      COMMAND_STATUS_SONG ": %i\n"
403
			      COMMAND_STATUS_SONGID ": %u\n",
404
			      song, getPlaylistSongId(song));
Avuton Olrich's avatar
Avuton Olrich committed
405
	}
406

Avuton Olrich's avatar
Avuton Olrich committed
407
	if (getPlayerState() != PLAYER_STATE_STOP) {
408
		const struct audio_format *af = player_get_audio_format();
409
		client_printf(client,
410 411
			      COMMAND_STATUS_TIME ": %i:%i\n"
			      COMMAND_STATUS_BITRATE ": %li\n"
412
			      COMMAND_STATUS_AUDIO ": %u:%u:%u\n",
413
			      getPlayerElapsedTime(), getPlayerTotalTime(),
414
			      getPlayerBitRate(),
415
			      af->sample_rate, af->bits, af->channels);
416
	}
Avuton Olrich's avatar
Avuton Olrich committed
417 418

	if ((updateJobId = isUpdatingDB())) {
419 420 421
		client_printf(client,
			      COMMAND_STATUS_UPDATING_DB ": %i\n",
			      updateJobId);
Warren Dukes's avatar
Warren Dukes committed
422
	}
423

Avuton Olrich's avatar
Avuton Olrich committed
424
	if (getPlayerError() != PLAYER_ERROR_NOERROR) {
425 426 427
		client_printf(client,
			      COMMAND_STATUS_ERROR ": %s\n",
			      getPlayerErrorStr());
Avuton Olrich's avatar
Avuton Olrich committed
428
	}
Warren Dukes's avatar
Warren Dukes committed
429

430
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
431 432
}

433
static enum command_return
434 435
handle_kill(G_GNUC_UNUSED struct client *client,
	    G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
436
{
Avuton Olrich's avatar
Avuton Olrich committed
437
	return COMMAND_RETURN_KILL;
Warren Dukes's avatar
Warren Dukes committed
438 439
}

440
static enum command_return
441 442
handle_close(G_GNUC_UNUSED struct client *client,
	     G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
443
{
Avuton Olrich's avatar
Avuton Olrich committed
444
	return COMMAND_RETURN_CLOSE;
Warren Dukes's avatar
Warren Dukes committed
445 446
}

447
static enum command_return
448
handle_add(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
449
{
450
	char *path = argv[1];
451
	enum playlist_result result;
452

453
	if (strncmp(path, "file:///", 8) == 0) {
454 455 456
#ifndef WIN32
		result = PLAYLIST_RESULT_DENIED;
#else
457
		result = playlist_append_file(path + 7, client_get_uid(client),
458
					      NULL);
459
#endif
460 461 462
		return print_playlist_result(client, result);
	}

Avuton Olrich's avatar
Avuton Olrich committed
463
	if (isRemoteUrl(path))
464
		return addToPlaylist(path, NULL);
Warren Dukes's avatar
Warren Dukes committed
465

466 467 468 469 470 471
	if (uri_has_scheme(path)) {
		command_error(client, ACK_ERROR_NO_EXIST,
			      "unsupported URI scheme");
		return COMMAND_RETURN_ERROR;
	}

472 473
	result = addAllIn(path);
	if (result == (enum playlist_result)-1) {
474 475
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
476
		return COMMAND_RETURN_ERROR;
477 478
	}

479
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
480 481
}

482
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
483
handle_addid(struct client *client, int argc, char *argv[])
484
{
485
	unsigned added_id;
486 487
	enum playlist_result result;

488 489 490
	if (strncmp(argv[1], "file:///", 8) == 0)
		result = playlist_append_file(argv[1] + 7,
					      client_get_uid(client),
491 492 493
					      &added_id);
	else
		result = addToPlaylist(argv[1], &added_id);
494

495
	if (result != PLAYLIST_RESULT_SUCCESS)
496
		return print_playlist_result(client, result);
497 498 499

	if (argc == 3) {
		int to;
500
		if (!check_int(client, &to, argv[2], check_integer, argv[2]))
501
			return COMMAND_RETURN_ERROR;
502 503
		result = moveSongInPlaylistById(added_id, to);
		if (result != PLAYLIST_RESULT_SUCCESS) {
504 505
			enum command_return ret =
				print_playlist_result(client, result);
506 507
			deleteFromPlaylistById(added_id);
			return ret;
508 509
		}
	}
510

511
	client_printf(client, "Id: %u\n", added_id);
512
	return result;
513 514
}

515
static enum command_return
516
handle_delete(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
517
{
Avuton Olrich's avatar
Avuton Olrich committed
518
	int song;
519
	enum playlist_result result;
Warren Dukes's avatar
Warren Dukes committed
520

521
	if (!check_int(client, &song, argv[1], need_positive))
522
		return COMMAND_RETURN_ERROR;
523 524

	result = deleteFromPlaylist(song);
525
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
526 527
}

528
static enum command_return
529
handle_deleteid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
530
{
Avuton Olrich's avatar
Avuton Olrich committed
531
	int id;
532
	enum playlist_result result;
533

534
	if (!check_int(client, &id, argv[1], need_positive))
535
		return COMMAND_RETURN_ERROR;
536 537

	result = deleteFromPlaylistById(id);
538
	return print_playlist_result(client, result);
539 540
}

541
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
542
handle_playlist(struct client *client,
543
	        G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
544
{
545
	showPlaylist(client);
546
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
547 548
}

549
static enum command_return
550 551
handle_shuffle(G_GNUC_UNUSED struct client *client,
	       G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
552
{
553
	shufflePlaylist();
554
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
555 556
}

557
static enum command_return
558 559
handle_clear(G_GNUC_UNUSED struct client *client,
	     G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
560
{
561
	clearPlaylist();
562
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
563 564
}

565
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
566
handle_save(struct client *client,
567
	    G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
568
{
569 570 571
	enum playlist_result result;

	result = savePlaylist(argv[1]);
572
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
573 574
}

575
static enum command_return
576
handle_load(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
577
{
578 579
	enum playlist_result result;

580
	result = loadPlaylist(client, argv[1]);
581
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
582
}
583

584
static enum command_return
585
handle_listplaylist(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
586
{
587 588
	int ret;

589
	ret = PlaylistInfo(client, argv[1], 0);
590
	if (ret == -1)
591
		command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
592 593

	return ret;
594
}
595

596
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
597
handle_listplaylistinfo(struct client *client,
598
			G_GNUC_UNUSED int argc, char *argv[])
599
{
600 601
	int ret;

602
	ret = PlaylistInfo(client, argv[1], 1);
603
	if (ret == -1)
604
		command_error(client, ACK_ERROR_NO_EXIST, "No such playlist");
605 606

	return ret;
607
}
Warren Dukes's avatar
Warren Dukes committed
608

609
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
610
handle_lsinfo(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
611
{
Max Kellermann's avatar
Max Kellermann committed
612
	const char *path = "";
613
	const struct directory *directory;
614 615 616 617

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

618
	directory = db_get_directory(path);
619
	if (directory == NULL) {
620 621
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory not found");
622
		return COMMAND_RETURN_ERROR;
623
	}
624

625 626
	directory_print(client, directory);

627 628 629 630 631 632 633
	if (isRootDirectory(path)) {
		GPtrArray *list = spl_list();
		if (list != NULL) {
			print_spl_list(client, list);
			spl_list_free(list);
		}
	}
634

635
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
636 637
}

638
static enum command_return
639
handle_rm(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
640
{
641 642
	enum playlist_result result;

643
	result = spl_delete(argv[1]);
644
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
645 646
}

647
static enum command_return
648
handle_rename(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
649
{
650 651
	enum playlist_result result;

652
	result = spl_rename(argv[1], argv[2]);
653
	return print_playlist_result(client, result);
654 655
}

656
static enum command_return
657
handle_plchanges(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
658
{
659
	uint32_t version;
660

661
	if (!check_uint32(client, &version, argv[1], need_positive))
662
		return COMMAND_RETURN_ERROR;
663
	return playlistChanges(client, version);
664 665
}

666
static enum command_return
667
handle_plchangesposid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
668
{
669
	uint32_t version;
670

671
	if (!check_uint32(client, &version, argv[1], need_positive))
672
		return COMMAND_RETURN_ERROR;
673
	return playlistChangesPosId(client, version);
674 675
}

676
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
677
handle_playlistinfo(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
678
{
Avuton Olrich's avatar
Avuton Olrich committed
679
	int song = -1;
680
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
681

682
	if (argc == 2 && !check_int(client, &song, argv[1], need_positive))
683
		return COMMAND_RETURN_ERROR;
684

685
	result = playlistInfo(client, song);
686
	return print_playlist_result(client, result);
Warren Dukes's avatar
Warren Dukes committed
687 688
}

689
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
690
handle_playlistid(struct client *client, int argc, char *argv[])
691
{
Avuton Olrich's avatar
Avuton Olrich committed
692
	int id = -1;
693
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
694

695
	if (argc == 2 && !check_int(client, &id, argv[1], need_positive))
696
		return COMMAND_RETURN_ERROR;
697

698
	result = playlistId(client, id);
699
	return print_playlist_result(client, result);
700 701
}

702
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
703
handle_find(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
704
{
705 706
	int ret;

Avuton Olrich's avatar
Avuton Olrich committed
707
	LocateTagItem *items;
708 709
	int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
							 argc - 1,
Avuton Olrich's avatar
Avuton Olrich committed
710
							 &items);
711

Avuton Olrich's avatar
Avuton Olrich committed
712
	if (numItems <= 0) {
713
		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
714
		return COMMAND_RETURN_ERROR;
715 716
	}

717
	ret = findSongsIn(client, NULL, numItems, items);
718
	if (ret == -1)
719 720
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
721

722
	freeLocateTagItemArray(numItems, items);
723 724

	return ret;
Warren Dukes's avatar
Warren Dukes committed
725 726
}

727
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
728
handle_search(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
729
{
730 731
	int ret;

Avuton Olrich's avatar
Avuton Olrich committed
732
	LocateTagItem *items;
733 734
	int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
							 argc - 1,
Avuton Olrich's avatar
Avuton Olrich committed
735
							 &items);
736

Avuton Olrich's avatar
Avuton Olrich committed
737
	if (numItems <= 0) {
738
		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
739
		return COMMAND_RETURN_ERROR;
740 741
	}

742
	ret = searchForSongsIn(client, NULL, numItems, items);
743
	if (ret == -1)
744 745
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
746

747
	freeLocateTagItemArray(numItems, items);
748 749

	return ret;
Warren Dukes's avatar
Warren Dukes committed
750 751
}

752
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
753
handle_count(struct client *client, int argc, char *argv[])
754 755 756 757 758 759 760 761 762
{
	int ret;

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

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

767
	ret = searchStatsForSongsIn(client, NULL, numItems, items);
768
	if (ret == -1)
769 770
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
771 772 773 774 775 776

	freeLocateTagItemArray(numItems, items);

	return ret;
}

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

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

790
	findSongsInPlaylist(client, numItems, items);
791 792 793

	freeLocateTagItemArray(numItems, items);

794
	return COMMAND_RETURN_OK;
795 796
}

797
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
798
handle_playlistsearch(struct client *client, int argc, char *argv[])
799 800 801 802 803 804 805
{
	LocateTagItem *items;
	int numItems = newLocateTagItemArrayFromArgArray(argv + 1,
							 argc - 1,
							 &items);

	if (numItems <= 0) {
806
		command_error(client, ACK_ERROR_ARG, "incorrect arguments");
807
		return COMMAND_RETURN_ERROR;
808 809
	}

810
	searchForSongsInPlaylist(client, numItems, items);
811 812 813

	freeLocateTagItemArray(numItems, items);

814
	return COMMAND_RETURN_OK;
815 816
}

817
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
818
handle_playlistdelete(struct client *client,
819
		      G_GNUC_UNUSED int argc, char *argv[]) {
820 821
	char *playlist = argv[1];
	int from;
822
	enum playlist_result result;
823

824
	if (!check_int(client, &from, argv[2], check_integer, argv[2]))
825
		return COMMAND_RETURN_ERROR;
826

827
	result = spl_remove_index(playlist, from);
828
	return print_playlist_result(client, result);
829 830
}

831
static enum command_return
832
handle_playlistmove(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
833 834 835
{
	char *playlist = argv[1];
	int from, to;
836
	enum playlist_result result;
837

838
	if (!check_int(client, &from, argv[2], check_integer, argv[2]))
839
		return COMMAND_RETURN_ERROR;
840
	if (!check_int(client, &to, argv[3], check_integer, argv[3]))
841
		return COMMAND_RETURN_ERROR;
842

843
	result = spl_move_index(playlist, from, to);
844
	return print_playlist_result(client, result);
845 846
}

847
static enum command_return
848
handle_update(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
849
{
850
	char *path = NULL;
851
	unsigned ret;
852

853
	assert(argc <= 2);
854 855
	if (argc == 2)
		path = g_strdup(argv[1]);
856 857 858 859

	ret = directory_update_init(path);
	if (ret > 0) {
		client_printf(client, "updating_db: %i\n", ret);
860
		return COMMAND_RETURN_OK;
861 862 863
	} else {
		command_error(client, ACK_ERROR_UPDATE_ALREADY,
			      "already updating");
864
		return COMMAND_RETURN_ERROR;
865
	}
Warren Dukes's avatar
Warren Dukes committed
866 867
}

868
static enum command_return
869 870
handle_next(G_GNUC_UNUSED struct client *client,
	    G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
871
{
872
	nextSongInPlaylist();
873
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
874 875
}

876
static enum command_return
877 878
handle_previous(G_GNUC_UNUSED struct client *client,
		G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
879
{
880
	previousSongInPlaylist();
881
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
882 883
}

884
static enum command_return
885
handle_listall(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
886
{
Avuton Olrich's avatar
Avuton Olrich committed
887
	char *directory = NULL;
888
	int ret;
Warren Dukes's avatar
Warren Dukes committed
889

890 891
	if (argc == 2)
		directory = argv[1];
892

893
	ret = printAllIn(client, directory);
894
	if (ret == -1)
895 896
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
897 898

	return ret;
Warren Dukes's avatar
Warren Dukes committed
899 900
}

901
static enum command_return
902
handle_volume(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
903
{
904
	int change, ret;
Warren Dukes's avatar
Warren Dukes committed
905

906
	if (!check_int(client, &change, argv[1], need_integer))
907
		return COMMAND_RETURN_ERROR;
908

909
	ret = volume_level_change(change, 1);
910
	if (ret == -1)
911 912
		command_error(client, ACK_ERROR_SYSTEM,
			      "problems setting volume");
913 914

	return ret;
Warren Dukes's avatar
Warren Dukes committed
915 916
}

917
static enum command_return
918
handle_setvol(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
919
{
920
	int level, ret;
Warren Dukes's avatar
Warren Dukes committed
921

922
	if (!check_int(client, &level, argv[1], need_integer))
923
		return COMMAND_RETURN_ERROR;
924

925
	ret = volume_level_change(level, 0);
926
	if (ret == -1)
927 928
		command_error(client, ACK_ERROR_SYSTEM,
			      "problems setting volume");
929 930

	return ret;
Warren Dukes's avatar
Warren Dukes committed
931 932
}

933
static enum command_return
934
handle_repeat(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
935
{
Avuton Olrich's avatar
Avuton Olrich committed
936
	int status;
Warren Dukes's avatar
Warren Dukes committed
937

938
	if (!check_int(client, &status, argv[1], need_integer))
939
		return COMMAND_RETURN_ERROR;
940 941

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

	setPlaylistRepeatStatus(status);
948
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
949 950
}

951
static enum command_return
952
handle_random(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
953
{
Avuton Olrich's avatar
Avuton Olrich committed
954
	int status;
Warren Dukes's avatar
Warren Dukes committed
955

956
	if (!check_int(client, &status, argv[1], need_integer))
957
		return COMMAND_RETURN_ERROR;
958 959

	if (status != 0 && status != 1) {
960 961
		command_error(client, ACK_ERROR_ARG,
			      "\"%i\" is not 0 or 1", status);
962
		return COMMAND_RETURN_ERROR;
963 964 965
	}

	setPlaylistRandomStatus(status);
966
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
967 968
}

969
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
970
handle_stats(struct client *client,
971
	     G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
972
{
973
	return printStats(client);
Warren Dukes's avatar
Warren Dukes committed
974 975
}

976
static enum command_return
977 978
handle_clearerror(G_GNUC_UNUSED struct client *client,
		  G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Warren Dukes's avatar
Warren Dukes committed
979
{
Avuton Olrich's avatar
Avuton Olrich committed
980
	clearPlayerError();
981
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
982 983
}

984
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
985
handle_list(struct client *client, int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
986
{
987
	int numConditionals;
Avuton Olrich's avatar
Avuton Olrich committed
988
	LocateTagItem *conditionals = NULL;
989
	int tagType = getLocateTagItemType(argv[1]);
990 991
	int ret;

Avuton Olrich's avatar
Avuton Olrich committed
992
	if (tagType < 0) {
993
		command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]);
994
		return COMMAND_RETURN_ERROR;
995 996
	}

997
	if (tagType == LOCATE_TAG_ANY_TYPE) {
998 999
		command_error(client, ACK_ERROR_ARG,
			      "\"any\" is not a valid return tag type");
1000
		return COMMAND_RETURN_ERROR;
1001 1002
	}

1003
	/* for compatibility with < 0.12.0 */
1004
	if (argc == 3) {
Avuton Olrich's avatar
Avuton Olrich committed
1005
		if (tagType != TAG_ITEM_ALBUM) {
1006 1007 1008
			command_error(client, ACK_ERROR_ARG,
				      "should be \"%s\" for 3 arguments",
				      mpdTagItemKeys[TAG_ITEM_ALBUM]);
1009
			return COMMAND_RETURN_ERROR;
1010 1011
		}
		conditionals = newLocateTagItem(mpdTagItemKeys[TAG_ITEM_ARTIST],
1012
						argv[2]);
1013
		numConditionals = 1;
Avuton Olrich's avatar
Avuton Olrich committed
1014 1015
	} else {
		numConditionals =
1016
		    newLocateTagItemArrayFromArgArray(argv + 2,
1017
						      argc - 2, &conditionals);
Avuton Olrich's avatar
Avuton Olrich committed
1018 1019

		if (numConditionals < 0) {
1020 1021
			command_error(client, ACK_ERROR_ARG,
				      "not able to parse args");
1022
			return COMMAND_RETURN_ERROR;
1023 1024
		}
	}
1025

1026
	ret = listAllUniqueTags(client, tagType, numConditionals, conditionals);
1027

Avuton Olrich's avatar
Avuton Olrich committed
1028 1029
	if (conditionals)
		freeLocateTagItemArray(numConditionals, conditionals);
Warren Dukes's avatar
Warren Dukes committed
1030

1031
	if (ret == -1)
1032 1033
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
1034

1035
	return ret;
Warren Dukes's avatar
Warren Dukes committed
1036 1037
}

1038
static enum command_return
1039
handle_move(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1040
{
1041
	int from, to;
1042
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1043

1044
	if (!check_int(client, &from, argv[1], check_integer, argv[1]))
1045
		return COMMAND_RETURN_ERROR;
1046
	if (!check_int(client, &to, argv[2], check_integer, argv[2]))
1047
		return COMMAND_RETURN_ERROR;
1048
	result = moveSongInPlaylist(from, to);
1049
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1050 1051
}

1052
static enum command_return
1053
handle_moveid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1054
{
1055
	int id, to;
1056
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1057

1058
	if (!check_int(client, &id, argv[1], check_integer, argv[1]))
1059
		return COMMAND_RETURN_ERROR;
1060
	if (!check_int(client, &to, argv[2], check_integer, argv[2]))
1061
		return COMMAND_RETURN_ERROR;
1062
	result = moveSongInPlaylistById(id, to);
1063
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1064 1065
}

1066
static enum command_return
1067
handle_swap(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1068
{
1069
	int song1, song2;
1070
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1071

1072
	if (!check_int(client, &song1, argv[1], check_integer, argv[1]))
1073
		return COMMAND_RETURN_ERROR;
1074
	if (!check_int(client, &song2, argv[2], check_integer, argv[2]))
1075
		return COMMAND_RETURN_ERROR;
1076
	result = swapSongsInPlaylist(song1, song2);
1077
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1078 1079
}

1080
static enum command_return
1081
handle_swapid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1082
{
1083
	int id1, id2;
1084
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1085

1086
	if (!check_int(client, &id1, argv[1], check_integer, argv[1]))
1087
		return COMMAND_RETURN_ERROR;
1088
	if (!check_int(client, &id2, argv[2], check_integer, argv[2]))
1089
		return COMMAND_RETURN_ERROR;
1090
	result = swapSongsInPlaylistById(id1, id2);
1091
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1092 1093
}

1094
static enum command_return
1095
handle_seek(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1096
{
1097
	int song, seek_time;
1098
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1099

1100
	if (!check_int(client, &song, argv[1], check_integer, argv[1]))
1101
		return COMMAND_RETURN_ERROR;
1102
	if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
1103
		return COMMAND_RETURN_ERROR;
1104 1105

	result = seekSongInPlaylist(song, seek_time);
1106
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1107 1108
}

1109
static enum command_return
1110
handle_seekid(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1111
{
1112
	int id, seek_time;
1113
	enum playlist_result result;
Avuton Olrich's avatar
Avuton Olrich committed
1114

1115
	if (!check_int(client, &id, argv[1], check_integer, argv[1]))
1116
		return COMMAND_RETURN_ERROR;
1117
	if (!check_int(client, &seek_time, argv[2], check_integer, argv[2]))
1118
		return COMMAND_RETURN_ERROR;
1119 1120

	result = seekSongInPlaylistById(id, seek_time);
1121
	return print_playlist_result(client, result);
Avuton Olrich's avatar
Avuton Olrich committed
1122 1123
}

1124
static enum command_return
1125
handle_listallinfo(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1126 1127
{
	char *directory = NULL;
1128
	int ret;
Avuton Olrich's avatar
Avuton Olrich committed
1129

1130 1131
	if (argc == 2)
		directory = argv[1];
1132

1133
	ret = printInfoForAllIn(client, directory);
1134
	if (ret == -1)
1135 1136
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
1137 1138

	return ret;
Avuton Olrich's avatar
Avuton Olrich committed
1139 1140
}

1141
static enum command_return
1142 1143
handle_ping(G_GNUC_UNUSED struct client *client,
	    G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1144
{
1145
	return COMMAND_RETURN_OK;
Avuton Olrich's avatar
Avuton Olrich committed
1146 1147
}

1148
static enum command_return
1149
handle_password(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1150
{
1151
	unsigned permission = 0;
1152 1153

	if (getPermissionFromPassword(argv[1], &permission) < 0) {
1154
		command_error(client, ACK_ERROR_PASSWORD, "incorrect password");
1155
		return COMMAND_RETURN_ERROR;
Warren Dukes's avatar
Warren Dukes committed
1156 1157
	}

1158 1159
	client_set_permission(client, permission);

1160
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
1161 1162
}

1163
static enum command_return
1164
handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
1165
{
1166
	unsigned xfade_time;
Warren Dukes's avatar
Warren Dukes committed
1167

1168
	if (!check_unsigned(client, &xfade_time, argv[1]))
1169
		return COMMAND_RETURN_ERROR;
Max Kellermann's avatar
Max Kellermann committed
1170
	setPlayerCrossFade(xfade_time);
Warren Dukes's avatar
Warren Dukes committed
1171

1172
	return COMMAND_RETURN_OK;
Warren Dukes's avatar
Warren Dukes committed
1173 1174
}

1175
static enum command_return
1176
handle_enableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
1177
{
1178 1179
	unsigned device;
	int ret;
1180

1181
	if (!check_unsigned(client, &device, argv[1]))
1182
		return COMMAND_RETURN_ERROR;
1183 1184 1185

	ret = enableAudioDevice(device);
	if (ret == -1)
1186 1187
		command_error(client, ACK_ERROR_NO_EXIST,
			      "No such audio output");
1188 1189

	return ret;
1190 1191
}

1192
static enum command_return
1193
handle_disableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
1194
{
1195 1196
	unsigned device;
	int ret;
1197

1198
	if (!check_unsigned(client, &device, argv[1]))
1199
		return COMMAND_RETURN_ERROR;
1200 1201 1202

	ret = disableAudioDevice(device);
	if (ret == -1)
1203 1204
		command_error(client, ACK_ERROR_NO_EXIST,
			      "No such audio output");
1205 1206

	return ret;
1207 1208
}

1209
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1210
handle_devices(struct client *client,
1211
	       G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
1212
{
1213
	printAudioDevices(client);
1214

1215
	return COMMAND_RETURN_OK;
1216 1217
}

1218
/* don't be fooled, this is the command handler for "commands" command */
1219
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1220
handle_commands(struct client *client,
1221
		G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]);
1222

1223
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1224
handle_not_commands(struct client *client,
1225
		    G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[]);
1226

1227
static enum command_return
1228
handle_playlistclear(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
1229
{
1230 1231
	enum playlist_result result;

1232
	result = spl_clear(argv[1]);
1233
	return print_playlist_result(client, result);
1234 1235
}

1236
static enum command_return
1237
handle_playlistadd(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
1238 1239 1240
{
	char *playlist = argv[1];
	char *path = argv[2];
1241
	enum playlist_result result;
1242 1243

	if (isRemoteUrl(path))
1244
		result = spl_append_uri(path, playlist);
1245 1246 1247 1248 1249
	else if (uri_has_scheme(path)) {
		command_error(client, ACK_ERROR_NO_EXIST,
			      "unsupported URI scheme");
		return COMMAND_RETURN_ERROR;
	} else
1250 1251 1252
		result = addAllInToStoredPlaylist(path, playlist);

	if (result == (enum playlist_result)-1) {
1253 1254
		command_error(client, ACK_ERROR_NO_EXIST,
			      "directory or file not found");
1255
		return COMMAND_RETURN_ERROR;
1256 1257
	}

1258
	return print_playlist_result(client, result);
1259 1260
}

1261
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1262
handle_listplaylists(struct client *client,
1263
		     G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
1264 1265 1266 1267 1268
{
	GPtrArray *list = spl_list();
	if (list == NULL) {
		command_error(client, ACK_ERROR_SYSTEM,
			      "failed to get list of stored playlists");
1269
		return COMMAND_RETURN_ERROR;
1270 1271 1272 1273
	}

	print_spl_list(client, list);
	spl_list_free(list);
1274
	return COMMAND_RETURN_OK;
1275 1276
}

1277
static enum command_return
1278
handle_idle(struct client *client,
1279
	    G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
1280
{
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300
        unsigned flags = 0, j;
        int i;
        const char *const* idle_names;

        idle_names = idle_get_names();
        for (i = 1; i < argc; ++i) {
                if (!argv[i])
                        continue;

                for (j = 0; idle_names[j]; ++j) {
                        if (!strcasecmp(argv[i], idle_names[j])) {
                                flags |= (1 << j);
                        }
                }
        }

        /* No argument means that the client wants to receive everything */
        if (flags == 0)
                flags = ~0;

1301
	/* enable "idle" mode on this client */
1302
	client_idle_wait(client, flags);
1303 1304 1305 1306 1307

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

1308 1309 1310 1311 1312 1313
/**
 * The command registry.
 *
 * This array must be sorted!
 */
static const struct command commands[] = {
Max Kellermann's avatar
Max Kellermann committed
1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327
	{ "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 },
1328
	{ "idle", PERMISSION_READ, 0, -1, handle_idle },
Max Kellermann's avatar
Max Kellermann committed
1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378
	{ "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 },
1379 1380 1381 1382 1383
};

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

/* don't be fooled, this is the command handler for "commands" command */
1384
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1385
handle_commands(struct client *client,
1386
		G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
1387 1388 1389 1390 1391 1392 1393
{
	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
1394
		if (cmd->permission == (permission & cmd->permission))
1395 1396 1397
			client_printf(client, "command: %s\n", cmd->cmd);
	}

1398
	return COMMAND_RETURN_OK;
1399 1400
}

1401
static enum command_return
Max Kellermann's avatar
Max Kellermann committed
1402
handle_not_commands(struct client *client,
1403
		    G_GNUC_UNUSED int argc, G_GNUC_UNUSED char *argv[])
1404 1405 1406 1407 1408 1409 1410
{
	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
1411
		if (cmd->permission != (permission & cmd->permission))
1412 1413 1414
			client_printf(client, "command: %s\n", cmd->cmd);
	}

1415
	return COMMAND_RETURN_OK;
1416 1417
}

Max Kellermann's avatar
Max Kellermann committed
1418
void command_init(void)
Avuton Olrich's avatar
Avuton Olrich committed
1419
{
1420 1421 1422 1423 1424
#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
1425 1426
}

Max Kellermann's avatar
Max Kellermann committed
1427
void command_finish(void)
Avuton Olrich's avatar
Avuton Olrich committed
1428
{
1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450
}

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
1451 1452
}

1453
static bool
Max Kellermann's avatar
Max Kellermann committed
1454 1455
command_check_request(const struct command *cmd, struct client *client,
		      unsigned permission, int argc, char *argv[])
Avuton Olrich's avatar
Avuton Olrich committed
1456 1457 1458
{
	int min = cmd->min + 1;
	int max = cmd->max + 1;
Warren Dukes's avatar
Warren Dukes committed
1459

Max Kellermann's avatar
Max Kellermann committed
1460
	if (cmd->permission != (permission & cmd->permission)) {
1461 1462 1463 1464
		if (client != NULL)
			command_error(client, ACK_ERROR_PERMISSION,
				      "you don't have permission for \"%s\"",
				      cmd->cmd);
1465
		return false;
Warren Dukes's avatar
Warren Dukes committed
1466 1467
	}

Avuton Olrich's avatar
Avuton Olrich committed
1468
	if (min == 0)
1469
		return true;
Warren Dukes's avatar
Warren Dukes committed
1470

Avuton Olrich's avatar
Avuton Olrich committed
1471
	if (min == max && max != argc) {
1472 1473 1474 1475
		if (client != NULL)
			command_error(client, ACK_ERROR_ARG,
				      "wrong number of arguments for \"%s\"",
				      argv[0]);
1476
		return false;
Avuton Olrich's avatar
Avuton Olrich committed
1477
	} else if (argc < min) {
1478 1479 1480
		if (client != NULL)
			command_error(client, ACK_ERROR_ARG,
				      "too few arguments for \"%s\"", argv[0]);
1481
		return false;
Avuton Olrich's avatar
Avuton Olrich committed
1482
	} else if (argc > max && max /* != 0 */ ) {
1483 1484 1485
		if (client != NULL)
			command_error(client, ACK_ERROR_ARG,
				      "too many arguments for \"%s\"", argv[0]);
1486
		return false;
Avuton Olrich's avatar
Avuton Olrich committed
1487
	} else
1488
		return true;
Warren Dukes's avatar
Warren Dukes committed
1489 1490
}

1491
static const struct command *
Max Kellermann's avatar
Max Kellermann committed
1492 1493
command_checked_lookup(struct client *client, unsigned permission,
		       int argc, char *argv[])
Warren Dukes's avatar
Warren Dukes committed
1494
{
Warren Dukes's avatar
Warren Dukes committed
1495
	static char unknown[] = "";
1496
	const struct command *cmd;
Warren Dukes's avatar
Warren Dukes committed
1497

Warren Dukes's avatar
Warren Dukes committed
1498 1499
	current_command = unknown;

1500
	if (argc == 0)
Avuton Olrich's avatar
Avuton Olrich committed
1501
		return NULL;
Warren Dukes's avatar
Warren Dukes committed
1502

1503 1504
	cmd = command_lookup(argv[0]);
	if (cmd == NULL) {
1505 1506 1507
		if (client != NULL)
			command_error(client, ACK_ERROR_UNKNOWN,
				      "unknown command \"%s\"", argv[0]);
Avuton Olrich's avatar
Avuton Olrich committed
1508 1509
		return NULL;
	}
Warren Dukes's avatar
Warren Dukes committed
1510

Warren Dukes's avatar
Warren Dukes committed
1511 1512
	current_command = cmd->cmd;

1513
	if (!command_check_request(cmd, client, permission, argc, argv))
1514 1515 1516 1517 1518
		return NULL;

	return cmd;
}

1519
enum command_return
Max Kellermann's avatar
Max Kellermann committed
1520
command_process(struct client *client, char *commandString)
1521
{
1522 1523
	int argc;
	char *argv[COMMAND_ARGV_MAX] = { NULL };
1524
	const struct command *cmd;
1525
	enum command_return ret = COMMAND_RETURN_ERROR;
1526

Eric Wong's avatar
Eric Wong committed
1527
	if (!(argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX)))
1528
		return COMMAND_RETURN_OK;
1529

Max Kellermann's avatar
Max Kellermann committed
1530 1531
	cmd = command_checked_lookup(client, client_get_permission(client),
				     argc, argv);
Eric Wong's avatar
Eric Wong committed
1532 1533
	if (cmd)
		ret = cmd->handler(client, argc, argv);
Warren Dukes's avatar
Warren Dukes committed
1534

Warren Dukes's avatar
Warren Dukes committed
1535 1536
	current_command = NULL;

1537 1538 1539
	return ret;
}

1540
enum command_return
Max Kellermann's avatar
Max Kellermann committed
1541
command_process_list(struct client *client,
1542
		     bool list_ok, GSList *list)
1543
{
1544
	enum command_return ret = COMMAND_RETURN_OK;
1545

Max Kellermann's avatar
Max Kellermann committed
1546
	command_list_num = 0;
Warren Dukes's avatar
Warren Dukes committed
1547

1548 1549 1550
	for (GSList *cur = list; cur != NULL; cur = g_slist_next(cur)) {
		char *cmd = cur->data;

Max Kellermann's avatar
Max Kellermann committed
1551
		DEBUG("command_process_list: process command \"%s\"\n",
1552 1553
		      cmd);
		ret = command_process(client, cmd);
Max Kellermann's avatar
Max Kellermann committed
1554
		DEBUG("command_process_list: command returned %i\n", ret);
1555
		if (ret != COMMAND_RETURN_OK || client_is_expired(client))
1556
			break;
Max Kellermann's avatar
Max Kellermann committed
1557
		else if (list_ok)
1558
			client_puts(client, "list_OK\n");
Max Kellermann's avatar
Max Kellermann committed
1559
		command_list_num++;
1560
	}
1561

Max Kellermann's avatar
Max Kellermann committed
1562
	command_list_num = 0;
1563
	return ret;
Warren Dukes's avatar
Warren Dukes committed
1564
}