OtherCommands.cxx 10.6 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2015 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"
#include "OtherCommands.hxx"
22 23
#include "FileCommands.hxx"
#include "StorageCommands.hxx"
24
#include "CommandError.hxx"
25
#include "db/Uri.hxx"
26
#include "storage/StorageInterface.hxx"
27
#include "DetachedSong.hxx"
Max Kellermann's avatar
Max Kellermann committed
28 29
#include "SongPrint.hxx"
#include "TagPrint.hxx"
30 31
#include "TagStream.hxx"
#include "tag/TagHandler.hxx"
Max Kellermann's avatar
Max Kellermann committed
32
#include "TimePrint.hxx"
33
#include "decoder/DecoderPrint.hxx"
Max Kellermann's avatar
Max Kellermann committed
34 35
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
Max Kellermann's avatar
Max Kellermann committed
36
#include "ls.hxx"
Max Kellermann's avatar
Max Kellermann committed
37
#include "mixer/Volume.hxx"
Max Kellermann's avatar
Max Kellermann committed
38
#include "util/UriUtil.hxx"
39
#include "util/Error.hxx"
40
#include "util/ConstBuffer.hxx"
41
#include "fs/AllocatedPath.hxx"
42
#include "Stats.hxx"
43
#include "Permission.hxx"
Max Kellermann's avatar
Max Kellermann committed
44
#include "PlaylistFile.hxx"
45
#include "db/PlaylistVector.hxx"
46
#include "client/Client.hxx"
47
#include "Partition.hxx"
48
#include "Instance.hxx"
Max Kellermann's avatar
Max Kellermann committed
49
#include "Idle.hxx"
50

51 52
#ifdef ENABLE_DATABASE
#include "DatabaseCommands.hxx"
53
#include "db/Interface.hxx"
54 55 56
#include "db/update/Service.hxx"
#endif

57 58 59 60
#include <assert.h>
#include <string.h>

static void
61
print_spl_list(Client &client, const PlaylistVector &list)
62
{
63 64
	for (const auto &i : list) {
		client_printf(client, "playlist: %s\n", i.name.c_str());
65

66 67
		if (i.mtime > 0)
			time_print(client, "Last-Modified", i.mtime);
68 69 70
	}
}

71
CommandResult
72
handle_urlhandlers(Client &client, gcc_unused ConstBuffer<const char *> args)
73
{
74
	if (client.IsLocal())
75 76
		client_puts(client, "handler: file://\n");
	print_supported_uri_schemes(client);
77
	return CommandResult::OK;
78 79
}

80
CommandResult
81
handle_decoders(Client &client, gcc_unused ConstBuffer<const char *> args)
82 83
{
	decoder_list_print(client);
84
	return CommandResult::OK;
85 86
}

87
CommandResult
88
handle_tagtypes(Client &client, gcc_unused ConstBuffer<const char *> args)
89 90
{
	tag_print_types(client);
91
	return CommandResult::OK;
92 93
}

94
CommandResult
95
handle_kill(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
96
{
97
	return CommandResult::KILL;
98 99
}

100
CommandResult
101
handle_close(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
102
{
103
	return CommandResult::FINISH;
104 105
}

106 107 108 109 110 111 112 113
static void
print_tag(TagType type, const char *value, void *ctx)
{
	Client &client = *(Client *)ctx;

	tag_print(client, type, value);
}

114
CommandResult
115
handle_listfiles(Client &client, ConstBuffer<const char *> args)
116
{
117 118
	/* default is root directory */
	const char *const uri = args.IsEmpty() ? "" : args.front();
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146

	if (memcmp(uri, "file:///", 8) == 0)
		/* list local directory */
		return handle_listfiles_local(client, uri + 7);

#ifdef ENABLE_DATABASE
	if (uri_has_scheme(uri))
		/* use storage plugin to list remote directory */
		return handle_listfiles_storage(client, uri);

	/* must be a path relative to the configured
	   music_directory */

	if (client.partition.instance.storage != nullptr)
		/* if we have a storage instance, obtain a list of
		   files from it */
		return handle_listfiles_storage(client,
						*client.partition.instance.storage,
						uri);

	/* fall back to entries from database if we have no storage */
	return handle_listfiles_db(client, uri);
#else
	command_error(client, ACK_ERROR_NO_EXIST, "No database");
	return CommandResult::ERROR;
#endif
}

147 148 149 150 151 152
static constexpr tag_handler print_tag_handler = {
	nullptr,
	print_tag,
	nullptr,
};

153
CommandResult
154
handle_lsinfo(Client &client, ConstBuffer<const char *> args)
155
{
156 157
	/* default is root directory */
	const char *const uri = args.IsEmpty() ? "" : args.front();
158

159
	if (memcmp(uri, "file:///", 8) == 0) {
160
		/* print information about an arbitrary local file */
161
		const char *path_utf8 = uri + 7;
162
		const auto path_fs = AllocatedPath::FromUTF8(path_utf8);
163 164 165 166

		if (path_fs.IsNull()) {
			command_error(client, ACK_ERROR_NO_EXIST,
				      "unsupported file name");
167
			return CommandResult::ERROR;
168
		}
169

170
		Error error;
171
		if (!client.AllowFile(path_fs, error))
172 173
			return print_error(client, error);

174 175
		DetachedSong song(path_utf8);
		if (!song.Update()) {
176 177
			command_error(client, ACK_ERROR_NO_EXIST,
				      "No such file");
178
			return CommandResult::ERROR;
179 180
		}

181
		song_print_info(client, song);
182
		return CommandResult::OK;
183 184
	}

185 186 187 188 189 190 191
	if (uri_has_scheme(uri)) {
		if (!uri_supported_scheme(uri)) {
			command_error(client, ACK_ERROR_NO_EXIST,
				      "unsupported URI scheme");
			return CommandResult::ERROR;
		}

192
		if (!tag_stream_scan(uri, print_tag_handler, &client)) {
193 194 195 196 197 198 199 200
			command_error(client, ACK_ERROR_NO_EXIST,
				      "No such file");
			return CommandResult::ERROR;
		}

		return CommandResult::OK;
	}

201
#ifdef ENABLE_DATABASE
202
	CommandResult result = handle_lsinfo2(client, args);
203
	if (result != CommandResult::OK)
204
		return result;
205
#endif
206 207

	if (isRootDirectory(uri)) {
208 209
		Error error;
		const auto &list = ListPlaylistFiles(error);
210
		print_spl_list(client, list);
211 212 213 214 215
	} else {
#ifndef ENABLE_DATABASE
		command_error(client, ACK_ERROR_NO_EXIST, "No database");
		return CommandResult::ERROR;
#endif
216 217
	}

218
	return CommandResult::OK;
219 220
}

221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
#ifdef ENABLE_DATABASE

static CommandResult
handle_update(Client &client, UpdateService &update,
	      const char *uri_utf8, bool discard)
{
	unsigned ret = update.Enqueue(uri_utf8, discard);
	if (ret > 0) {
		client_printf(client, "updating_db: %i\n", ret);
		return CommandResult::OK;
	} else {
		command_error(client, ACK_ERROR_UPDATE_ALREADY,
			      "already updating");
		return CommandResult::ERROR;
	}
}

238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
static CommandResult
handle_update(Client &client, Database &db,
	      const char *uri_utf8, bool discard)
{
	Error error;
	unsigned id = db.Update(uri_utf8, discard, error);
	if (id > 0) {
		client_printf(client, "updating_db: %i\n", id);
		return CommandResult::OK;
	} else if (error.IsDefined()) {
		return print_error(client, error);
	} else {
		/* Database::Update() has returned 0 without setting
		   the Error: the method is not implemented */
		command_error(client, ACK_ERROR_NO_EXIST, "Not implemented");
		return CommandResult::ERROR;
	}
}

257 258
#endif

259
static CommandResult
260
handle_update(Client &client, ConstBuffer<const char *> args, bool discard)
261
{
262
#ifdef ENABLE_DATABASE
263
	const char *path = "";
264

265 266 267
	assert(args.size <= 1);
	if (!args.IsEmpty()) {
		path = args.front();
268 269 270

		if (*path == 0 || strcmp(path, "/") == 0)
			/* backwards compatibility with MPD 0.15 */
271
			path = "";
272 273 274
		else if (!uri_safe_local(path)) {
			command_error(client, ACK_ERROR_ARG,
				      "Malformed path");
275
			return CommandResult::ERROR;
276 277 278
		}
	}

279
	UpdateService *update = client.partition.instance.update;
280 281
	if (update != nullptr)
		return handle_update(client, *update, path, discard);
282 283 284 285

	Database *db = client.partition.instance.database;
	if (db != nullptr)
		return handle_update(client, *db, path, discard);
286
#else
287
	(void)args;
288
	(void)discard;
289
#endif
290 291 292

	command_error(client, ACK_ERROR_NO_EXIST, "No database");
	return CommandResult::ERROR;
293 294
}

295
CommandResult
296
handle_update(Client &client, gcc_unused ConstBuffer<const char *> args)
297
{
298
	return handle_update(client, args, false);
299
}
300

301
CommandResult
302
handle_rescan(Client &client, gcc_unused ConstBuffer<const char *> args)
303
{
304
	return handle_update(client, args, true);
305 306
}

307
CommandResult
308
handle_setvol(Client &client, ConstBuffer<const char *> args)
309 310 311 312
{
	unsigned level;
	bool success;

313
	if (!check_unsigned(client, &level, args.front()))
314
		return CommandResult::ERROR;
315 316 317

	if (level > 100) {
		command_error(client, ACK_ERROR_ARG, "Invalid volume value");
318
		return CommandResult::ERROR;
319 320
	}

321
	success = volume_level_change(client.partition.outputs, level);
322 323 324
	if (!success) {
		command_error(client, ACK_ERROR_SYSTEM,
			      "problems setting volume");
325
		return CommandResult::ERROR;
326 327
	}

328
	return CommandResult::OK;
329 330
}

331
CommandResult
332
handle_volume(Client &client, ConstBuffer<const char *> args)
333 334
{
	int relative;
335
	if (!check_int(client, &relative, args.front()))
336 337 338 339 340 341 342
		return CommandResult::ERROR;

	if (relative < -100 || relative > 100) {
		command_error(client, ACK_ERROR_ARG, "Invalid volume value");
		return CommandResult::ERROR;
	}

343
	const int old_volume = volume_level_get(client.partition.outputs);
344 345 346 347 348 349 350 351 352 353 354
	if (old_volume < 0) {
		command_error(client, ACK_ERROR_SYSTEM, "No mixer");
		return CommandResult::ERROR;
	}

	int new_volume = old_volume + relative;
	if (new_volume < 0)
		new_volume = 0;
	else if (new_volume > 100)
		new_volume = 100;

355 356
	if (new_volume != old_volume &&
	    !volume_level_change(client.partition.outputs, new_volume)) {
357 358 359 360 361 362 363 364
		command_error(client, ACK_ERROR_SYSTEM,
			      "problems setting volume");
		return CommandResult::ERROR;
	}

	return CommandResult::OK;
}

365
CommandResult
366
handle_stats(Client &client, gcc_unused ConstBuffer<const char *> args)
367 368
{
	stats_print(client);
369
	return CommandResult::OK;
370 371
}

372
CommandResult
373
handle_ping(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args)
374
{
375
	return CommandResult::OK;
376 377
}

378
CommandResult
379
handle_password(Client &client, ConstBuffer<const char *> args)
380 381 382
{
	unsigned permission = 0;

383
	if (getPermissionFromPassword(args.front(), &permission) < 0) {
384
		command_error(client, ACK_ERROR_PASSWORD, "incorrect password");
385
		return CommandResult::ERROR;
386 387
	}

388
	client.SetPermission(permission);
389

390
	return CommandResult::OK;
391 392
}

393
CommandResult
394
handle_config(Client &client, gcc_unused ConstBuffer<const char *> args)
395
{
396
	if (!client.IsLocal()) {
397 398
		command_error(client, ACK_ERROR_PERMISSION,
			      "Command only permitted to local clients");
399
		return CommandResult::ERROR;
400 401
	}

402
#ifdef ENABLE_DATABASE
403 404 405 406 407
	const Storage *storage = client.GetStorage();
	if (storage != nullptr) {
		const auto path = storage->MapUTF8("");
		client_printf(client, "music_directory: %s\n", path.c_str());
	}
408
#endif
409

410
	return CommandResult::OK;
411 412
}

413
CommandResult
414
handle_idle(Client &client, ConstBuffer<const char *> args)
415
{
416
	unsigned flags = 0;
417

418 419
	for (const char *i : args) {
		unsigned event = idle_parse_name(i);
420 421 422
		if (event == 0) {
			command_error(client, ACK_ERROR_ARG,
				      "Unrecognized idle event: %s",
423
				      i);
424
			return CommandResult::ERROR;
425
		}
426 427

		flags |= event;
428 429 430 431 432 433 434
	}

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

	/* enable "idle" mode on this client */
435
	client.IdleWait(flags);
436

437
	return CommandResult::IDLE;
438
}