OtherCommands.cxx 9.83 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2019 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
#include "Request.hxx"
23 24
#include "FileCommands.hxx"
#include "StorageCommands.hxx"
25
#include "db/Uri.hxx"
26
#include "storage/StorageInterface.hxx"
27
#include "LocateUri.hxx"
28
#include "song/DetachedSong.hxx"
Max Kellermann's avatar
Max Kellermann committed
29 30
#include "SongPrint.hxx"
#include "TagPrint.hxx"
31
#include "TagStream.hxx"
32
#include "tag/Handler.hxx"
Max Kellermann's avatar
Max Kellermann committed
33
#include "TimePrint.hxx"
34
#include "decoder/DecoderPrint.hxx"
Max Kellermann's avatar
Max Kellermann committed
35
#include "ls.hxx"
Max Kellermann's avatar
Max Kellermann committed
36
#include "mixer/Volume.hxx"
37
#include "time/ChronoUtil.hxx"
Max Kellermann's avatar
Max Kellermann committed
38
#include "util/UriUtil.hxx"
39
#include "util/StringAPI.hxx"
40
#include "util/StringView.hxx"
41
#include "fs/AllocatedPath.hxx"
42
#include "Stats.hxx"
Max Kellermann's avatar
Max Kellermann committed
43
#include "PlaylistFile.hxx"
44
#include "db/PlaylistVector.hxx"
45
#include "client/Client.hxx"
46
#include "client/Response.hxx"
47
#include "Partition.hxx"
48
#include "Instance.hxx"
49
#include "IdleFlags.hxx"
50
#include "Log.hxx"
51

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

58 59 60
#include <assert.h>

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

66
		if (!IsNegative(i.mtime))
67
			time_print(r, "Last-Modified", i.mtime);
68 69 70
	}
}

71
CommandResult
72
handle_urlhandlers(Client &client, gcc_unused Request args, Response &r)
73
{
74
	if (client.IsLocal())
75 76
		r.Format("handler: file://\n");
	print_supported_uri_schemes(r);
77
	return CommandResult::OK;
78 79
}

80
CommandResult
81 82
handle_decoders(gcc_unused Client &client, gcc_unused Request args,
		Response &r)
83
{
84
	decoder_list_print(r);
85
	return CommandResult::OK;
86 87
}

88
CommandResult
89 90
handle_kill(gcc_unused Client &client, gcc_unused Request request,
	    gcc_unused Response &r)
91
{
92
	return CommandResult::KILL;
93 94
}

95
CommandResult
96
handle_listfiles(Client &client, Request args, Response &r)
97
{
98
	/* default is root directory */
99
	const auto uri = args.GetOptional(0, "");
100

101
	const auto located_uri = LocateUri(UriPluginKind::STORAGE, uri, &client
102
#ifdef ENABLE_DATABASE
103
					   , nullptr
104
#endif
105
					   );
106

107 108 109 110
	switch (located_uri.type) {
	case LocatedUri::Type::ABSOLUTE:
#ifdef ENABLE_DATABASE
		/* use storage plugin to list remote directory */
111 112
		return handle_listfiles_storage(client, r,
						located_uri.canonical_uri);
113 114 115 116
#else
		r.Error(ACK_ERROR_NO_EXIST, "No database");
		return CommandResult::ERROR;
#endif
117

118 119
	case LocatedUri::Type::RELATIVE:
#ifdef ENABLE_DATABASE
120
		if (client.GetInstance().storage != nullptr)
121 122 123
			/* if we have a storage instance, obtain a list of
			   files from it */
			return handle_listfiles_storage(r,
124
							*client.GetInstance().storage,
125 126 127 128
							uri);

		/* fall back to entries from database if we have no storage */
		return handle_listfiles_db(client, r, uri);
129
#else
130 131
		r.Error(ACK_ERROR_NO_EXIST, "No database");
		return CommandResult::ERROR;
132
#endif
133 134 135

	case LocatedUri::Type::PATH:
		/* list local directory */
136
		return handle_listfiles_local(r, located_uri.path);
137 138 139
	}

	gcc_unreachable();
140 141
}

142 143 144 145 146 147 148
class PrintTagHandler final : public NullTagHandler {
	Response &response;

public:
	explicit PrintTagHandler(Response &_response) noexcept
		:NullTagHandler(WANT_TAG), response(_response) {}

149
	void OnTag(TagType type, StringView value) noexcept override {
150 151 152
		if (response.GetClient().tag_mask.Test(type))
			tag_print(response, type, value);
	}
153 154
};

155 156
static CommandResult
handle_lsinfo_absolute(Response &r, const char *uri)
157
{
158 159
	PrintTagHandler h(r);
	if (!tag_stream_scan(uri, h)) {
160 161
		r.Error(ACK_ERROR_NO_EXIST, "No such file");
		return CommandResult::ERROR;
162 163
	}

164 165
	return CommandResult::OK;
}
166

167 168 169
static CommandResult
handle_lsinfo_relative(Client &client, Response &r, const char *uri)
{
170
#ifdef ENABLE_DATABASE
171
	CommandResult result = handle_lsinfo2(client, uri, r);
172
	if (result != CommandResult::OK)
173
		return result;
174 175
#else
	(void)client;
176
#endif
177 178

	if (isRootDirectory(uri)) {
179 180
		try {
			print_spl_list(r, ListPlaylistFiles());
181 182
		} catch (...) {
			LogError(std::current_exception());
183
		}
184 185
	} else {
#ifndef ENABLE_DATABASE
186
		r.Error(ACK_ERROR_NO_EXIST, "No database");
187 188
		return CommandResult::ERROR;
#endif
189 190
	}

191
	return CommandResult::OK;
192 193
}

194
static CommandResult
195
handle_lsinfo_path(Client &, Response &r,
196 197 198 199 200 201 202 203
		   const char *path_utf8, Path path_fs)
{
	DetachedSong song(path_utf8);
	if (!song.LoadFile(path_fs)) {
		r.Error(ACK_ERROR_NO_EXIST, "No such file");
		return CommandResult::ERROR;
	}

204
	song_print_info(r, song);
205 206 207 208 209 210 211
	return CommandResult::OK;
}

CommandResult
handle_lsinfo(Client &client, Request args, Response &r)
{
	/* default is root directory */
212 213 214 215 216 217 218 219
	auto uri = args.GetOptional(0, "");
	if (StringIsEqual(uri, "/"))
		/* this URI is malformed, but some clients are buggy
		   and use "lsinfo /" to list files in the music root
		   directory, which was never intended to work, but
		   once did; in order to retain backwards
		   compatibility, work around this here */
		uri = "";
220

221
	const auto located_uri = LocateUri(UriPluginKind::INPUT, uri, &client
222
#ifdef ENABLE_DATABASE
223
					   , nullptr
224
#endif
225
					   );
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243

	switch (located_uri.type) {
	case LocatedUri::Type::ABSOLUTE:
		return handle_lsinfo_absolute(r, located_uri.canonical_uri);

	case LocatedUri::Type::RELATIVE:
		return handle_lsinfo_relative(client, r,
					      located_uri.canonical_uri);

	case LocatedUri::Type::PATH:
		/* print information about an arbitrary local file */
		return handle_lsinfo_path(client, r, located_uri.canonical_uri,
					  located_uri.path);
	}

	gcc_unreachable();
}

244 245 246
#ifdef ENABLE_DATABASE

static CommandResult
247
handle_update(Response &r, UpdateService &update,
248 249 250
	      const char *uri_utf8, bool discard)
{
	unsigned ret = update.Enqueue(uri_utf8, discard);
251 252
	r.Format("updating_db: %i\n", ret);
	return CommandResult::OK;
253 254
}

255
static CommandResult
256
handle_update(Response &r, Database &db,
257 258
	      const char *uri_utf8, bool discard)
{
259
	unsigned id = db.Update(uri_utf8, discard);
260
	if (id > 0) {
261
		r.Format("updating_db: %i\n", id);
262 263 264 265
		return CommandResult::OK;
	} else {
		/* Database::Update() has returned 0 without setting
		   the Error: the method is not implemented */
266
		r.Error(ACK_ERROR_NO_EXIST, "Not implemented");
267 268 269 270
		return CommandResult::ERROR;
	}
}

271 272
#endif

273
static CommandResult
274
handle_update(Client &client, Request args, Response &r, bool discard)
275
{
276
#ifdef ENABLE_DATABASE
277
	const char *path = "";
278

279
	assert(args.size <= 1);
280
	if (!args.empty()) {
281
		path = args.front();
282

283
		if (*path == 0 || StringIsEqual(path, "/"))
284
			/* backwards compatibility with MPD 0.15 */
285
			path = "";
286
		else if (!uri_safe_local(path)) {
287
			r.Error(ACK_ERROR_ARG, "Malformed path");
288
			return CommandResult::ERROR;
289 290 291
		}
	}

292
	UpdateService *update = client.GetInstance().update;
293
	if (update != nullptr)
294
		return handle_update(r, *update, path, discard);
295

296
	Database *db = client.GetInstance().GetDatabase();
297
	if (db != nullptr)
298
		return handle_update(r, *db, path, discard);
299
#else
300
	(void)client;
301
	(void)args;
302
	(void)discard;
303
#endif
304

305
	r.Error(ACK_ERROR_NO_EXIST, "No database");
306
	return CommandResult::ERROR;
307 308
}

309
CommandResult
310
handle_update(Client &client, Request args, gcc_unused Response &r)
311
{
312
	return handle_update(client, args, r, false);
313
}
314

315
CommandResult
316
handle_rescan(Client &client, Request args, Response &r)
317
{
318
	return handle_update(client, args, r, true);
319 320
}

321
CommandResult
322
handle_setvol(Client &client, Request args, Response &r)
323
{
324
	unsigned level = args.ParseUnsigned(0, 100);
325

326
	if (!volume_level_change(client.GetPartition().outputs, level)) {
327
		r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
328
		return CommandResult::ERROR;
329 330
	}

331
	return CommandResult::OK;
332 333
}

334
CommandResult
335
handle_volume(Client &client, Request args, Response &r)
336
{
337
	int relative = args.ParseInt(0, -100, 100);
338

339 340 341
	auto &outputs = client.GetPartition().outputs;

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

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

353
	if (new_volume != old_volume &&
354
	    !volume_level_change(outputs, new_volume)) {
355
		r.Error(ACK_ERROR_SYSTEM, "problems setting volume");
356 357 358 359 360 361
		return CommandResult::ERROR;
	}

	return CommandResult::OK;
}

362
CommandResult
363
handle_stats(Client &client, gcc_unused Request args, Response &r)
364
{
365
	stats_print(r, client.GetPartition());
366
	return CommandResult::OK;
367 368
}

369
CommandResult
370
handle_config(Client &client, gcc_unused Request args, Response &r)
371
{
372
	if (!client.IsLocal()) {
373 374
		r.Error(ACK_ERROR_PERMISSION,
			"Command only permitted to local clients");
375
		return CommandResult::ERROR;
376 377
	}

378
#ifdef ENABLE_DATABASE
379 380 381
	const Storage *storage = client.GetStorage();
	if (storage != nullptr) {
		const auto path = storage->MapUTF8("");
382
		r.Format("music_directory: %s\n", path.c_str());
383
	}
384
#endif
385

386
	return CommandResult::OK;
387 388
}

389
CommandResult
390
handle_idle(Client &client, Request args, Response &r)
391
{
392
	unsigned flags = 0;
393 394
	for (const char *i : args) {
		unsigned event = idle_parse_name(i);
395
		if (event == 0) {
396 397
			r.FormatError(ACK_ERROR_ARG,
				      "Unrecognized idle event: %s", i);
398
			return CommandResult::ERROR;
399
		}
400 401

		flags |= event;
402 403 404 405 406 407 408
	}

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

	/* enable "idle" mode on this client */
409
	client.IdleWait(flags);
410

411
	return CommandResult::IDLE;
412
}