.github
LICENSES
android
build
doc
python
src
subprojects
systemd
test
fs
fuzzer
net
playlist
tag
time
util
ConfigGlue.hxx
ContainerScan.cxx
DumpDatabase.cxx
DumpDecoderClient.cxx
DumpDecoderClient.hxx
DumpOgg.cxx
LoadDatabase.cxx
MakeTag.hxx
NullMixerListener.hxx
ParseSongFilter.cxx
ReadApeTags.cxx
ReadFrames.cxx
ReadFrames.hxx
RunChromaprint.cxx
RunCurl.cxx
RunMixRampAnalyzer.cxx
RunReplayGainAnalyzer.cxx
RunZeroconf.cxx
ShutdownHandler.cxx
ShutdownHandler.hxx
TestAudioFormat.cxx
TestIcu.cxx
TestRewindInputStream.cxx
TestStringFilter.cxx
TestTagSongFilter.cxx
WriteFile.cxx
dump_playlist.cxx
dump_rva2.cxx
dump_text_file.cxx
meson.build
read_conf.cxx
read_mixer.cxx
read_tags.cxx
run_convert.cxx
run_decoder.cxx
run_encoder.cxx
run_filter.cxx
run_gunzip.cxx
run_gzip.cxx
run_inotify.cxx
run_input.cxx
run_neighbor_explorer.cxx
run_normalize.cxx
run_output.cxx
run_resolver.cxx
run_storage.cxx
software_volume.cxx
test_archive_bzip2.sh
test_archive_iso9660.sh
test_archive_zzip.sh
test_icy_parser.cxx
test_pcm_channels.cxx
test_pcm_dither.cxx
test_pcm_export.cxx
test_pcm_format.cxx
test_pcm_interleave.cxx
test_pcm_mix.cxx
test_pcm_pack.cxx
test_pcm_util.hxx
test_pcm_volume.cxx
test_protocol.cxx
test_queue_priority.cxx
test_translate_song.cxx
test_vorbis_encoder.cxx
visit_archive.cxx
win32
.clang-format
.gitignore
.readthedocs.yaml
AUTHORS
COPYING
NEWS
README.md
meson.build
meson_options.txt
mpd.svg
shell.nix
valgrind.suppressions

This reverts commit b49cfe96f4
. It was
a bad idea because it broke signal handlers. I need to find a better
way to fix io_uring intialization.
261 lines
5.2 KiB
C++
261 lines
5.2 KiB
C++
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
// Copyright The Music Player Daemon Project
|
|
|
|
#include "config.h"
|
|
#include "TagSave.hxx"
|
|
#include "tag/Tag.hxx"
|
|
#include "ConfigGlue.hxx"
|
|
#include "input/InputStream.hxx"
|
|
#include "input/Init.hxx"
|
|
#include "input/Registry.hxx"
|
|
#include "input/InputPlugin.hxx"
|
|
#include "input/RemoteTagScanner.hxx"
|
|
#include "input/ScanTags.hxx"
|
|
#include "event/Thread.hxx"
|
|
#include "thread/Cond.hxx"
|
|
#include "Log.hxx"
|
|
#include "LogBackend.hxx"
|
|
#include "fs/Path.hxx"
|
|
#include "fs/NarrowPath.hxx"
|
|
#include "io/BufferedOutputStream.hxx"
|
|
#include "io/FileDescriptor.hxx"
|
|
#include "io/StdioOutputStream.hxx"
|
|
#include "cmdline/OptionDef.hxx"
|
|
#include "cmdline/OptionParser.hxx"
|
|
#include "util/PrintException.hxx"
|
|
|
|
#ifdef ENABLE_ARCHIVE
|
|
#include "archive/ArchiveList.hxx"
|
|
#endif
|
|
|
|
#include <stdexcept>
|
|
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
|
|
static constexpr std::size_t MAX_CHUNK_SIZE = 16384;
|
|
|
|
struct CommandLine {
|
|
const char *uri = nullptr;
|
|
|
|
FromNarrowPath config_path;
|
|
|
|
std::size_t seek = 0;
|
|
|
|
std::size_t chunk_size = MAX_CHUNK_SIZE;
|
|
|
|
bool verbose = false;
|
|
|
|
bool scan = false;
|
|
};
|
|
|
|
enum Option {
|
|
OPTION_CONFIG,
|
|
OPTION_VERBOSE,
|
|
OPTION_SCAN,
|
|
OPTION_SEEK,
|
|
OPTION_CHUNK_SIZE,
|
|
};
|
|
|
|
static constexpr OptionDef option_defs[] = {
|
|
{"config", 0, true, "Load a MPD configuration file"},
|
|
{"verbose", 'v', false, "Verbose logging"},
|
|
{"scan", 0, false, "Scan tags instead of reading raw data"},
|
|
{"seek", 0, true, "Start reading at this position"},
|
|
{"chunk-size", 0, true, "Read this number of bytes at a time"},
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
static CommandLine
|
|
ParseCommandLine(int argc, char **argv)
|
|
{
|
|
CommandLine c;
|
|
|
|
OptionParser option_parser(option_defs, argc, argv);
|
|
while (auto o = option_parser.Next()) {
|
|
switch (Option(o.index)) {
|
|
case OPTION_CONFIG:
|
|
c.config_path = o.value;
|
|
break;
|
|
|
|
case OPTION_VERBOSE:
|
|
c.verbose = true;
|
|
break;
|
|
|
|
case OPTION_SCAN:
|
|
c.scan = true;
|
|
break;
|
|
|
|
case OPTION_SEEK:
|
|
c.seek = ParseSize(o.value);
|
|
break;
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
auto args = option_parser.GetRemaining();
|
|
if (args.size() != 1)
|
|
throw std::runtime_error("Usage: run_input [--verbose] [--config=FILE] [--scan] [--chunk-size=BYTES] URI");
|
|
|
|
c.uri = args.front();
|
|
return c;
|
|
}
|
|
|
|
class GlobalInit {
|
|
const ConfigData config;
|
|
EventThread io_thread;
|
|
|
|
#ifdef ENABLE_ARCHIVE
|
|
const ScopeArchivePluginsInit archive_plugins_init{config};
|
|
#endif
|
|
|
|
const ScopeInputPluginsInit input_plugins_init{config, io_thread.GetEventLoop()};
|
|
|
|
public:
|
|
explicit GlobalInit(Path config_path)
|
|
:config(AutoLoadConfigFile(config_path))
|
|
{
|
|
io_thread.Start();
|
|
}
|
|
};
|
|
|
|
static void
|
|
tag_save(FILE *file, const Tag &tag)
|
|
{
|
|
StdioOutputStream sos(file);
|
|
WithBufferedOutputStream(sos, [&](auto &bos){
|
|
tag_save(bos, tag);
|
|
});
|
|
}
|
|
|
|
static int
|
|
dump_input_stream(InputStream &is, FileDescriptor out,
|
|
offset_type seek, size_t chunk_size)
|
|
{
|
|
out.SetBinaryMode();
|
|
|
|
std::unique_lock lock{is.mutex};
|
|
|
|
if (seek > 0)
|
|
is.Seek(lock, seek);
|
|
|
|
/* print meta data */
|
|
|
|
if (is.HasMimeType())
|
|
fprintf(stderr, "MIME type: %s\n", is.GetMimeType());
|
|
|
|
/* read data and tags from the stream */
|
|
|
|
while (!is.IsEOF()) {
|
|
{
|
|
auto tag = is.ReadTag();
|
|
if (tag) {
|
|
fprintf(stderr, "Received a tag:\n");
|
|
tag_save(stderr, *tag);
|
|
}
|
|
}
|
|
|
|
std::byte buffer[MAX_CHUNK_SIZE];
|
|
assert(chunk_size <= sizeof(buffer));
|
|
size_t num_read = is.Read(lock, {buffer, chunk_size});
|
|
if (num_read == 0)
|
|
break;
|
|
|
|
out.FullWrite({buffer, num_read});
|
|
}
|
|
|
|
is.Check();
|
|
|
|
return 0;
|
|
}
|
|
|
|
class DumpRemoteTagHandler final : public RemoteTagHandler {
|
|
Mutex mutex;
|
|
Cond cond;
|
|
|
|
Tag tag;
|
|
std::exception_ptr error;
|
|
|
|
bool done = false;
|
|
|
|
public:
|
|
Tag Wait() {
|
|
std::unique_lock lock{mutex};
|
|
cond.wait(lock, [this]{ return done; });
|
|
|
|
if (error)
|
|
std::rethrow_exception(error);
|
|
|
|
return std::move(tag);
|
|
}
|
|
|
|
/* virtual methods from RemoteTagHandler */
|
|
void OnRemoteTag(Tag &&_tag) noexcept override {
|
|
const std::scoped_lock lock{mutex};
|
|
tag = std::move(_tag);
|
|
done = true;
|
|
cond.notify_all();
|
|
}
|
|
|
|
void OnRemoteTagError(std::exception_ptr e) noexcept override {
|
|
const std::scoped_lock lock{mutex};
|
|
error = std::move(e);
|
|
done = true;
|
|
cond.notify_all();
|
|
}
|
|
};
|
|
|
|
static int
|
|
Scan(const char *uri)
|
|
{
|
|
DumpRemoteTagHandler handler;
|
|
|
|
auto scanner = InputScanTags(uri, handler);
|
|
if (!scanner) {
|
|
fprintf(stderr, "Unsupported URI\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
scanner->Start();
|
|
tag_save(stdout, handler.Wait());
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
try {
|
|
const auto c = ParseCommandLine(argc, argv);
|
|
|
|
/* initialize MPD */
|
|
|
|
SetLogThreshold(c.verbose ? LogLevel::DEBUG : LogLevel::INFO);
|
|
const GlobalInit init(c.config_path);
|
|
|
|
if (c.scan)
|
|
return Scan(c.uri);
|
|
|
|
/* open the stream and dump it */
|
|
|
|
Mutex mutex;
|
|
auto is = InputStream::OpenReady(c.uri, mutex);
|
|
return dump_input_stream(*is, FileDescriptor(STDOUT_FILENO),
|
|
c.seek, c.chunk_size);
|
|
} catch (...) {
|
|
PrintException(std::current_exception());
|
|
return EXIT_FAILURE;
|
|
}
|