diff --git a/Makefile.am b/Makefile.am index 44c70fb82..9f02ebb3e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1277,6 +1277,7 @@ libinput_a_SOURCES += \ src/input/IcyInputStream.cxx src/input/IcyInputStream.hxx \ src/input/plugins/CurlInputPlugin.cxx src/input/plugins/CurlInputPlugin.hxx \ src/lib/curl/Easy.hxx \ + src/lib/curl/Multi.hxx \ src/IcyMetaDataParser.cxx src/IcyMetaDataParser.hxx endif diff --git a/src/input/plugins/CurlInputPlugin.cxx b/src/input/plugins/CurlInputPlugin.cxx index 3d31fd747..727e7f718 100644 --- a/src/input/plugins/CurlInputPlugin.cxx +++ b/src/input/plugins/CurlInputPlugin.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "CurlInputPlugin.hxx" #include "lib/curl/Easy.hxx" +#include "lib/curl/Multi.hxx" #include "../AsyncInputStream.hxx" #include "../IcyInputStream.hxx" #include "../InputPlugin.hxx" @@ -194,14 +195,10 @@ private: * Manager for the global CURLM object. */ class CurlGlobal final : private TimeoutMonitor { - CURLM *const multi; + CurlMulti multi; public: - CurlGlobal(EventLoop &_loop, CURLM *_multi); - - ~CurlGlobal() { - curl_multi_cleanup(multi); - } + explicit CurlGlobal(EventLoop &_loop); void Add(CurlInputStream *c); void Remove(CurlInputStream *c); @@ -214,7 +211,7 @@ public: void ReadInfo(); void Assign(curl_socket_t fd, CurlSocket &cs) { - curl_multi_assign(multi, fd, &cs); + curl_multi_assign(multi.Get(), fd, &cs); } void SocketAction(curl_socket_t fd, int ev_bitmask); @@ -230,7 +227,7 @@ public: */ void ResumeSockets() { int running_handles; - curl_multi_socket_all(multi, &running_handles); + curl_multi_socket_all(multi.Get(), &running_handles); } private: @@ -258,15 +255,14 @@ static CurlGlobal *curl_global; static constexpr Domain curl_domain("curl"); static constexpr Domain curlm_domain("curlm"); -CurlGlobal::CurlGlobal(EventLoop &_loop, CURLM *_mutex) - :TimeoutMonitor(_loop), multi(_mutex) +CurlGlobal::CurlGlobal(EventLoop &_loop) + :TimeoutMonitor(_loop) { - curl_multi_setopt(multi, CURLMOPT_SOCKETFUNCTION, - CurlSocket::SocketFunction); - curl_multi_setopt(multi, CURLMOPT_SOCKETDATA, this); + multi.SetOption(CURLMOPT_SOCKETFUNCTION, CurlSocket::SocketFunction); + multi.SetOption(CURLMOPT_SOCKETDATA, this); - curl_multi_setopt(multi, CURLMOPT_TIMERFUNCTION, TimerFunction); - curl_multi_setopt(multi, CURLMOPT_TIMERDATA, this); + multi.SetOption(CURLMOPT_TIMERFUNCTION, TimerFunction); + multi.SetOption(CURLMOPT_TIMERDATA, this); } /** @@ -366,7 +362,7 @@ CurlGlobal::Add(CurlInputStream *c) assert(c != nullptr); assert(c->easy); - CURLMcode mcode = curl_multi_add_handle(multi, c->easy.Get()); + CURLMcode mcode = curl_multi_add_handle(multi.Get(), c->easy.Get()); if (mcode != CURLM_OK) throw FormatRuntimeError("curl_multi_add_handle() failed: %s", curl_multi_strerror(mcode)); @@ -394,7 +390,7 @@ input_curl_easy_add_indirect(CurlInputStream *c) inline void CurlGlobal::Remove(CurlInputStream *c) { - curl_multi_remove_handle(multi, c->easy.Get()); + curl_multi_remove_handle(multi.Get(), c->easy.Get()); } void @@ -467,7 +463,7 @@ void CurlGlobal::SocketAction(curl_socket_t fd, int ev_bitmask) { int running_handles; - CURLMcode mcode = curl_multi_socket_action(multi, fd, ev_bitmask, + CURLMcode mcode = curl_multi_socket_action(multi.Get(), fd, ev_bitmask, &running_handles); if (mcode != CURLM_OK) FormatError(curlm_domain, @@ -490,7 +486,7 @@ CurlGlobal::ReadInfo() CURLMsg *msg; int msgs_in_queue; - while ((msg = curl_multi_info_read(multi, + while ((msg = curl_multi_info_read(multi.Get(), &msgs_in_queue)) != nullptr) { if (msg->msg == CURLMSG_DONE) input_curl_handle_done(msg->easy_handle, msg->data.result); @@ -501,7 +497,7 @@ int CurlGlobal::TimerFunction(gcc_unused CURLM *_global, long timeout_ms, void *userp) { auto &global = *(CurlGlobal *)userp; - assert(_global == global.multi); + assert(_global == global.multi.Get()); if (timeout_ms < 0) { global.Cancel(); @@ -566,14 +562,14 @@ input_curl_init(const ConfigBlock &block) verify_peer = block.GetBlockValue("verify_peer", true); verify_host = block.GetBlockValue("verify_host", true); - CURLM *global = curl_multi_init(); - if (global == nullptr) { + try { + curl_global = new CurlGlobal(io_thread_get()); + } catch (const std::runtime_error &e) { + LogError(e); curl_slist_free_all(http_200_aliases); curl_global_cleanup(); throw PluginUnavailable("curl_multi_init() failed"); } - - curl_global = new CurlGlobal(io_thread_get(), global); } static void diff --git a/src/lib/curl/Multi.hxx b/src/lib/curl/Multi.hxx new file mode 100644 index 000000000..fa8d311da --- /dev/null +++ b/src/lib/curl/Multi.hxx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2016 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 CURL_MULTI_HXX +#define CURL_MULTI_HXX + +#include + +#include +#include +#include + +/** + * An OO wrapper for a "CURLM*" (a libCURL "multi" handle). + */ +class CurlMulti { + CURLM *handle = nullptr; + +public: + /** + * Allocate a new CURLM*. + * + * Throws std::runtime_error on error. + */ + CurlMulti() + :handle(curl_multi_init()) + { + if (handle == nullptr) + throw std::runtime_error("curl_multi_init() failed"); + } + + /** + * Create an empty instance. + */ + CurlMulti(std::nullptr_t):handle(nullptr) {} + + CurlMulti(CurlMulti &&src):handle(std::exchange(src.handle, nullptr)) {} + + ~CurlMulti() { + if (handle != nullptr) + curl_multi_cleanup(handle); + } + + operator bool() const { + return handle != nullptr; + } + + CurlMulti &operator=(CurlMulti &&src) { + std::swap(handle, src.handle); + return *this; + } + + CURL *Get() { + return handle; + } + + template + void SetOption(CURLMoption option, T value) { + auto code = curl_multi_setopt(handle, option, value); + if (code != CURLM_OK) + throw std::runtime_error(curl_multi_strerror(code)); + } +}; + +#endif