run_input.cxx 5.66 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * 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.
 */

20
#include "config.h"
21
#include "TagSave.hxx"
22
#include "tag/Tag.hxx"
23
#include "ConfigGlue.hxx"
Max Kellermann's avatar
Max Kellermann committed
24 25
#include "input/InputStream.hxx"
#include "input/Init.hxx"
26 27 28
#include "input/Registry.hxx"
#include "input/InputPlugin.hxx"
#include "input/RemoteTagScanner.hxx"
29
#include "input/ScanTags.hxx"
30
#include "event/Thread.hxx"
31
#include "thread/Cond.hxx"
32
#include "Log.hxx"
33
#include "LogBackend.hxx"
34
#include "fs/Path.hxx"
35
#include "fs/NarrowPath.hxx"
36 37
#include "fs/io/BufferedOutputStream.hxx"
#include "fs/io/StdioOutputStream.hxx"
38
#include "util/ConstBuffer.hxx"
39
#include "util/OptionDef.hxx"
40
#include "util/OptionParser.hxx"
41
#include "util/PrintException.hxx"
42

43
#ifdef ENABLE_ARCHIVE
44
#include "archive/ArchiveList.hxx"
45 46
#endif

47 48
#include <stdexcept>

49
#include <unistd.h>
50
#include <stdlib.h>
51

52 53
static constexpr std::size_t MAX_CHUNK_SIZE = 16384;

54 55
struct CommandLine {
	const char *uri = nullptr;
56

57
	FromNarrowPath config_path;
58

59 60
	std::size_t chunk_size = MAX_CHUNK_SIZE;

61
	bool verbose = false;
62 63

	bool scan = false;
64 65 66 67
};

enum Option {
	OPTION_CONFIG,
68
	OPTION_VERBOSE,
69
	OPTION_SCAN,
70
	OPTION_CHUNK_SIZE,
71 72 73 74
};

static constexpr OptionDef option_defs[] = {
	{"config", 0, true, "Load a MPD configuration file"},
75
	{"verbose", 'v', false, "Verbose logging"},
76
	{"scan", 0, false, "Scan tags instead of reading raw data"},
77
	{"chunk-size", 0, true, "Read this number of bytes at a time"},
78 79
};

80 81 82 83 84 85 86 87 88 89 90
static std::size_t
ParseSize(const char *s)
{
	char *endptr;
	std::size_t value = std::strtoul(s, &endptr, 10);
	if (endptr == s)
		throw std::runtime_error("Failed to parse integer");

	return value;
}

91 92 93 94 95
static CommandLine
ParseCommandLine(int argc, char **argv)
{
	CommandLine c;

96
	OptionParser option_parser(option_defs, argc, argv);
97
	while (auto o = option_parser.Next()) {
98 99
		switch (Option(o.index)) {
		case OPTION_CONFIG:
100
			c.config_path = o.value;
101
			break;
102 103 104 105

		case OPTION_VERBOSE:
			c.verbose = true;
			break;
106 107 108 109

		case OPTION_SCAN:
			c.scan = true;
			break;
110 111 112 113 114 115

		case OPTION_CHUNK_SIZE:
			c.chunk_size = ParseSize(o.value);
			if (c.chunk_size <= 0 || c.chunk_size > MAX_CHUNK_SIZE)
				throw std::runtime_error("Invalid chunk size");
			break;
116
		}
117 118 119 120
	}

	auto args = option_parser.GetRemaining();
	if (args.size != 1)
121
		throw std::runtime_error("Usage: run_input [--verbose] [--config=FILE] URI");
122 123 124 125 126

	c.uri = args.front();
	return c;
}

127
class GlobalInit {
128
	const ConfigData config;
129
	EventThread io_thread;
130

131 132 133 134
#ifdef ENABLE_ARCHIVE
	const ScopeArchivePluginsInit archive_plugins_init;
#endif

135 136
	const ScopeInputPluginsInit input_plugins_init;

137
public:
138
	explicit GlobalInit(Path config_path)
139 140
		:config(AutoLoadConfigFile(config_path)),
		 input_plugins_init(config, io_thread.GetEventLoop())
141
	{
142
		io_thread.Start();
143 144 145
	}
};

146 147 148 149
static void
tag_save(FILE *file, const Tag &tag)
{
	StdioOutputStream sos(file);
150 151 152
	WithBufferedOutputStream(sos, [&](auto &bos){
		tag_save(bos, tag);
	});
153 154
}

155
static int
156
dump_input_stream(InputStream &is, FileDescriptor out, size_t chunk_size)
157
{
158
	std::unique_lock<Mutex> lock(is.mutex);
159

160 161
	/* print meta data */

162 163
	if (is.HasMimeType())
		fprintf(stderr, "MIME type: %s\n", is.GetMimeType());
164 165 166

	/* read data and tags from the stream */

167
	while (!is.IsEOF()) {
168
		{
169
			auto tag = is.ReadTag();
170 171 172 173
			if (tag) {
				fprintf(stderr, "Received a tag:\n");
				tag_save(stderr, *tag);
			}
174 175
		}

176 177
		char buffer[MAX_CHUNK_SIZE];
		assert(chunk_size <= sizeof(buffer));
178
		size_t num_read = is.Read(lock, buffer, chunk_size);
179
		if (num_read == 0)
180 181
			break;

182
		out.FullWrite(buffer, num_read);
183 184
	}

185
	is.Check();
186

187 188 189
	return 0;
}

190 191 192 193 194 195 196 197 198 199 200
class DumpRemoteTagHandler final : public RemoteTagHandler {
	Mutex mutex;
	Cond cond;

	Tag tag;
	std::exception_ptr error;

	bool done = false;

public:
	Tag Wait() {
201
		std::unique_lock<Mutex> lock(mutex);
202
		cond.wait(lock, [this]{ return done; });
203 204 205 206 207 208 209 210 211 212 213 214

		if (error)
			std::rethrow_exception(error);

		return std::move(tag);
	}

	/* virtual methods from RemoteTagHandler */
	void OnRemoteTag(Tag &&_tag) noexcept override {
		const std::lock_guard<Mutex> lock(mutex);
		tag = std::move(_tag);
		done = true;
215
		cond.notify_all();
216 217 218 219 220 221
	}

	void OnRemoteTagError(std::exception_ptr e) noexcept override {
		const std::lock_guard<Mutex> lock(mutex);
		error = std::move(e);
		done = true;
222
		cond.notify_all();
223 224 225 226 227 228 229 230
	}
};

static int
Scan(const char *uri)
{
	DumpRemoteTagHandler handler;

231 232 233 234
	auto scanner = InputScanTags(uri, handler);
	if (!scanner) {
		fprintf(stderr, "Unsupported URI\n");
		return EXIT_FAILURE;
235 236
	}

237 238 239
	scanner->Start();
	tag_save(stdout, handler.Wait());
	return EXIT_SUCCESS;
240 241
}

242
int main(int argc, char **argv)
243
try {
244
	const auto c = ParseCommandLine(argc, argv);
245 246 247

	/* initialize MPD */

248 249
	SetLogThreshold(c.verbose ? LogLevel::DEBUG : LogLevel::INFO);
	const GlobalInit init(c.config_path);
250

251 252 253
	if (c.scan)
		return Scan(c.uri);

254 255
	/* open the stream and dump it */

256
	Mutex mutex;
257
	auto is = InputStream::OpenReady(c.uri, mutex);
258 259
	return dump_input_stream(*is, FileDescriptor(STDOUT_FILENO),
				 c.chunk_size);
260 261
} catch (...) {
	PrintException(std::current_exception());
262
	return EXIT_FAILURE;
263
}