diff --git a/Makefile.am b/Makefile.am index 3e956279d..d19987fd0 100644 --- a/Makefile.am +++ b/Makefile.am @@ -244,6 +244,7 @@ CURL_SOURCES = \ YAJL_SOURCES = \ src/lib/yajl/ParseInputStream.cxx src/lib/yajl/ParseInputStream.hxx \ + src/lib/yajl/Callbacks.hxx \ src/lib/yajl/Handle.hxx UPNP_SOURCES = \ diff --git a/src/lib/yajl/Callbacks.hxx b/src/lib/yajl/Callbacks.hxx new file mode 100644 index 000000000..4bc60426d --- /dev/null +++ b/src/lib/yajl/Callbacks.hxx @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2018 Max Kellermann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef YAJL_CALLBACKS_HXX +#define YAJL_CALLBACKS_HXX + +#include "util/Cast.hxx" +#include "util/StringView.hxx" + +#include + +namespace Yajl { + +/** + * Helper template which allows implementing callbacks as regular + * methods. The "ctx" parameter is casted to the enclosing class. + */ +template +struct CallbacksWrapper { + static T &Cast(void *ctx) { + return *(T *)ctx; + } + + static int Integer(void *ctx, long long integerVal) noexcept { + return Cast(ctx).Integer(integerVal); + } + + static int String(void *ctx, const unsigned char *stringVal, + size_t stringLen) noexcept { + return Cast(ctx).String(StringView((const char *)stringVal, + stringLen)); + } + + static int StartMap(void *ctx) noexcept { + return Cast(ctx).StartMap(); + } + + static int MapKey(void *ctx, const unsigned char *key, + size_t stringLen) noexcept { + return Cast(ctx).MapKey(StringView((const char *)key, + stringLen)); + } + + static int EndMap(void *ctx) noexcept { + return Cast(ctx).EndMap(); + } +}; + +} // namespace Yajl + +#endif diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx index ad88868b1..2ecaff20d 100644 --- a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx +++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx @@ -22,6 +22,7 @@ #include "../PlaylistPlugin.hxx" #include "../MemorySongEnumerator.hxx" #include "lib/yajl/Handle.hxx" +#include "lib/yajl/Callbacks.hxx" #include "lib/yajl/ParseInputStream.hxx" #include "config/Block.hxx" #include "input/InputStream.hxx" @@ -109,111 +110,106 @@ struct SoundCloudJsonData { int got_url = 0; /* nesting level of last stream_url */ std::forward_list songs; + + bool Integer(long long value) noexcept; + bool String(StringView value) noexcept; + bool StartMap() noexcept; + bool MapKey(StringView value) noexcept; + bool EndMap() noexcept; }; -static int -handle_integer(void *ctx, long long intval) +inline bool +SoundCloudJsonData::Integer(long long intval) noexcept { - auto *data = (SoundCloudJsonData *) ctx; - - switch (data->key) { + switch (key) { case SoundCloudJsonData::Key::DURATION: - data->duration = intval; + duration = intval; break; default: break; } - return 1; + return true; } -static int -handle_string(void *ctx, const unsigned char *stringval, size_t stringlen) +inline bool +SoundCloudJsonData::String(StringView value) noexcept { - auto *data = (SoundCloudJsonData *) ctx; - const char *s = (const char *) stringval; - - switch (data->key) { + switch (key) { case SoundCloudJsonData::Key::TITLE: - data->title.assign(s, stringlen); + title.assign(value.data, value.size); break; case SoundCloudJsonData::Key::STREAM_URL: - data->stream_url.assign(s, stringlen); - data->got_url = 1; + stream_url.assign(value.data, value.size); + got_url = 1; break; default: break; } - return 1; + return true; } -static int -handle_mapkey(void *ctx, const unsigned char *stringval, size_t stringlen) +inline bool +SoundCloudJsonData::MapKey(StringView value) noexcept { - auto *data = (SoundCloudJsonData *) ctx; - const auto *i = key_str; - while (*i != nullptr && - !StringStartsWith(*i, {(const char *)stringval, stringlen})) + while (*i != nullptr && !StringStartsWith(*i, value)) ++i; - data->key = SoundCloudJsonData::Key(i - key_str); - return 1; + key = SoundCloudJsonData::Key(i - key_str); + return true; } -static int -handle_start_map(void *ctx) +inline bool +SoundCloudJsonData::StartMap() noexcept { - auto *data = (SoundCloudJsonData *) ctx; + if (got_url > 0) + got_url++; - if (data->got_url > 0) - data->got_url++; - - return 1; + return true; } -static int -handle_end_map(void *ctx) +inline bool +SoundCloudJsonData::EndMap() noexcept { - auto *data = (SoundCloudJsonData *) ctx; - - if (data->got_url > 1) { - data->got_url--; + if (got_url > 1) { + got_url--; return 1; } - if (data->got_url == 0) + if (got_url == 0) return 1; /* got_url == 1, track finished, make it into a song */ - data->got_url = 0; + got_url = 0; - const std::string u = data->stream_url + "?client_id=" + + const std::string u = stream_url + "?client_id=" + soundcloud_config.apikey; TagBuilder tag; - tag.SetDuration(SignedSongTime::FromMS(data->duration)); - if (!data->title.empty()) - tag.AddItem(TAG_NAME, data->title.c_str()); + tag.SetDuration(SignedSongTime::FromMS(duration)); + if (!title.empty()) + tag.AddItem(TAG_NAME, title.c_str()); - data->songs.emplace_front(u.c_str(), tag.Commit()); + songs.emplace_front(u.c_str(), tag.Commit()); - return 1; + return true; } +using Wrapper = Yajl::CallbacksWrapper; static constexpr yajl_callbacks parse_callbacks = { nullptr, nullptr, - handle_integer, + Wrapper::Integer, nullptr, nullptr, - handle_string, - handle_start_map, - handle_mapkey, - handle_end_map, + Wrapper::String, + Wrapper::StartMap, + Wrapper::MapKey, + Wrapper::EndMap, nullptr, nullptr, };