diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx index 1413d5374..96d5be4b1 100644 --- a/src/CommandLine.cxx +++ b/src/CommandLine.cxx @@ -154,6 +154,10 @@ static void version() for (; *suffixes != nullptr; ++suffixes) printf(" %s", *suffixes); + if (plugin.protocols != nullptr) + for (const auto &i : plugin.protocols()) + printf(" %s", i.c_str()); + printf("\n"); }); diff --git a/src/decoder/DecoderPlugin.cxx b/src/decoder/DecoderPlugin.cxx index d7e6de357..bbb3966fc 100644 --- a/src/decoder/DecoderPlugin.cxx +++ b/src/decoder/DecoderPlugin.cxx @@ -18,10 +18,24 @@ */ #include "DecoderPlugin.hxx" +#include "util/StringCompare.hxx" #include "util/StringUtil.hxx" +#include #include +bool +DecoderPlugin::SupportsUri(const char *uri) const noexcept +{ + if (protocols != nullptr) { + const auto p = protocols(); + return std::any_of(p.begin(), p.end(), [uri](const auto &schema) + { return StringStartsWithIgnoreCase(uri, schema.c_str()); } ); + } + + return false; +} + bool DecoderPlugin::SupportsSuffix(const char *suffix) const noexcept { diff --git a/src/decoder/DecoderPlugin.hxx b/src/decoder/DecoderPlugin.hxx index da4409e47..c09aa5032 100644 --- a/src/decoder/DecoderPlugin.hxx +++ b/src/decoder/DecoderPlugin.hxx @@ -23,6 +23,8 @@ #include "util/Compiler.h" #include // IWYU pragma: export +#include +#include struct ConfigBlock; class InputStream; @@ -50,6 +52,16 @@ struct DecoderPlugin { */ void (*finish)() noexcept = nullptr; + /** + * Return a set of supported protocols. + */ + std::set (*protocols)() noexcept = nullptr; + + /** + * Decode an URI with a protocol listed in protocols(). + */ + void (*uri_decode)(DecoderClient &client, const char *uri) = nullptr; + /** * Decode a stream (data read from an #InputStream object). * @@ -143,6 +155,14 @@ struct DecoderPlugin { return copy; } + constexpr auto WithProtocols(std::set (*_protocols)() noexcept, + void (*_uri_decode)(DecoderClient &client, const char *uri)) noexcept { + auto copy = *this; + copy.protocols = _protocols; + copy.uri_decode = _uri_decode; + return copy; + } + constexpr auto WithSuffixes(const char *const*_suffixes) noexcept { auto copy = *this; copy.suffixes = _suffixes; @@ -183,6 +203,13 @@ struct DecoderPlugin { stream_decode(client, is); } + /** + * Decode an URI which is supported (check SupportsUri()). + */ + void UriDecode(DecoderClient &client, const char *uri) const { + uri_decode(client, uri); + } + /** * Decode a file. */ @@ -218,6 +245,9 @@ struct DecoderPlugin { return container_scan(path, tnum); } + gcc_pure + bool SupportsUri(const char *uri) const noexcept; + /** * Does the plugin announce the specified file name suffix? */ diff --git a/src/decoder/Thread.cxx b/src/decoder/Thread.cxx index 9167cd421..d4d85edb4 100644 --- a/src/decoder/Thread.cxx +++ b/src/decoder/Thread.cxx @@ -46,6 +46,42 @@ static constexpr Domain decoder_thread_domain("decoder_thread"); +/** + * Decode a URI with the given decoder plugin. + * + * Caller holds DecoderControl::mutex. + */ +static bool +DecoderUriDecode(const DecoderPlugin &plugin, + DecoderBridge &bridge, const char *uri) +{ + assert(plugin.uri_decode != nullptr); + assert(bridge.stream_tag == nullptr); + assert(bridge.decoder_tag == nullptr); + assert(uri != nullptr); + assert(bridge.dc.state == DecoderState::START); + + FormatDebug(decoder_thread_domain, "probing plugin %s", plugin.name); + + if (bridge.dc.command == DecoderCommand::STOP) + throw StopDecoder(); + + { + const ScopeUnlock unlock(bridge.dc.mutex); + + FormatThreadName("decoder:%s", plugin.name); + + plugin.UriDecode(bridge, uri); + + SetThreadName("decoder"); + } + + assert(bridge.dc.state == DecoderState::START || + bridge.dc.state == DecoderState::DECODE); + + return bridge.dc.state != DecoderState::START; +} + /** * Decode a stream with the given decoder plugin. * @@ -236,6 +272,24 @@ MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is) LoadReplayGain(bridge, is); } +/** + * Try decoding a URI. + * + * DecoderControl::mutex is not be locked by caller. + */ +static bool +TryUriDecode(DecoderBridge &bridge, const char *uri) +{ + return decoder_plugins_try([&bridge, uri](const DecoderPlugin &plugin){ + if (!plugin.SupportsUri(uri)) + return false; + + std::unique_lock lock(bridge.dc.mutex); + bridge.Reset(); + return DecoderUriDecode(plugin, bridge, uri); + }); +} + /** * Try decoding a stream. * @@ -244,6 +298,9 @@ MaybeLoadReplayGain(DecoderBridge &bridge, InputStream &is) static bool decoder_run_stream(DecoderBridge &bridge, const char *uri) { + if (TryUriDecode(bridge, uri)) + return true; + DecoderControl &dc = bridge.dc; auto input_stream = bridge.OpenUri(uri); diff --git a/src/ls.cxx b/src/ls.cxx index 022ccb370..5a1536c3d 100644 --- a/src/ls.cxx +++ b/src/ls.cxx @@ -21,6 +21,8 @@ #include "ls.hxx" #include "input/Registry.hxx" #include "input/InputPlugin.hxx" +#include "decoder/DecoderList.hxx" +#include "decoder/DecoderPlugin.hxx" #include "client/Response.hxx" #include "util/UriExtract.hxx" @@ -39,6 +41,11 @@ void print_supported_uri_schemes_to_fp(FILE *fp) protocols.emplace(uri); }); + decoder_plugins_for_each([&protocols](const auto &plugin){ + if (plugin.protocols != nullptr) + protocols.merge(plugin.protocols()); + }); + for (const auto& protocol : protocols) { fprintf(fp, " %s", protocol.c_str()); } @@ -54,6 +61,11 @@ print_supported_uri_schemes(Response &r) protocols.emplace(uri); }); + decoder_plugins_for_each_enabled([&protocols](const auto &plugin){ + if (plugin.protocols != nullptr) + protocols.merge(plugin.protocols()); + }); + for (const auto& protocol : protocols) { r.Format("handler: %s\n", protocol.c_str()); } @@ -68,5 +80,7 @@ uri_supported_scheme(const char *uri) noexcept if (plugin->SupportsUri(uri)) return true; - return false; + return decoder_plugins_try([uri](const auto &plugin){ + return plugin.SupportsUri(uri); + }); } diff --git a/test/run_decoder.cxx b/test/run_decoder.cxx index 1147e4faf..a7e275302 100644 --- a/test/run_decoder.cxx +++ b/test/run_decoder.cxx @@ -204,7 +204,12 @@ try { } MyDecoderClient client(c.seek_where); - if (plugin->file_decode != nullptr) { + if (plugin->SupportsUri(c.uri)) { + try { + plugin->UriDecode(client, c.uri); + } catch (StopDecoder) { + } + } else if (plugin->file_decode != nullptr) { try { plugin->FileDecode(client, FromNarrowPath(c.uri)); } catch (StopDecoder) {