diff --git a/resource_retriever/CMakeLists.txt b/resource_retriever/CMakeLists.txt index 9e05996..f5e8ee3 100644 --- a/resource_retriever/CMakeLists.txt +++ b/resource_retriever/CMakeLists.txt @@ -17,8 +17,12 @@ find_package(libcurl_vendor REQUIRED) find_package(CURL REQUIRED) # TODO(wjwwood): split cpp and python apis into separate packages - -add_library(${PROJECT_NAME} src/retriever.cpp) +add_library(${PROJECT_NAME} + src/retriever.cpp + src/plugins/retriever_plugin.cpp + src/plugins/filesystem_retriever.cpp + src/plugins/curl_retriever.cpp +) target_include_directories(${PROJECT_NAME} PUBLIC $ diff --git a/resource_retriever/include/resource_retriever/exception.hpp b/resource_retriever/include/resource_retriever/exception.hpp new file mode 100644 index 0000000..1a075f6 --- /dev/null +++ b/resource_retriever/include/resource_retriever/exception.hpp @@ -0,0 +1,49 @@ +// Copyright 2009, Willow Garage, Inc. All rights reserved. +// +// 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. +// +// * Neither the name of the Willow Garage, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// 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 COPYRIGHT HOLDER 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 RESOURCE_RETRIEVER__EXCEPTION_HPP_ +#define RESOURCE_RETRIEVER__EXCEPTION_HPP_ + +#include +#include + +#include "resource_retriever/visibility_control.hpp" + +namespace resource_retriever +{ +class RESOURCE_RETRIEVER_PUBLIC Exception : public std::runtime_error +{ +public: + Exception(const std::string & file, const std::string & error_msg) + : std::runtime_error("Error retrieving file [" + file + "]: " + error_msg) + { + } +}; +} // namespace resource_retriever + +#endif // RESOURCE_RETRIEVER__EXCEPTION_HPP_ diff --git a/resource_retriever/include/resource_retriever/memory_resource.hpp b/resource_retriever/include/resource_retriever/memory_resource.hpp new file mode 100644 index 0000000..dc38446 --- /dev/null +++ b/resource_retriever/include/resource_retriever/memory_resource.hpp @@ -0,0 +1,64 @@ +// Copyright 2009, Willow Garage, Inc. All rights reserved. +// +// 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. +// +// * Neither the name of the Willow Garage, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// 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 COPYRIGHT HOLDER 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 RESOURCE_RETRIEVER__MEMORY_RESOURCE_HPP_ +#define RESOURCE_RETRIEVER__MEMORY_RESOURCE_HPP_ + +#include +#include +#include +#include + +#include "resource_retriever/visibility_control.hpp" + +namespace resource_retriever +{ +/** + * \brief A combination of a pointer to data in memory along with the data's size. + */ +struct RESOURCE_RETRIEVER_PUBLIC MemoryResource +{ + explicit MemoryResource( + std::string url_in, + std::string expanded_url_in, + std::vector data_in) + : url(std::move(url_in)), + expanded_url(std::move(expanded_url_in)), + data(std::move(data_in)) + { + } + const std::string url; + const std::string expanded_url; + const std::vector data; +}; + +using MemoryResourcePtr = std::shared_ptr; + +} // namespace resource_retriever + +#endif // RESOURCE_RETRIEVER__MEMORY_RESOURCE_HPP_ diff --git a/resource_retriever/include/resource_retriever/plugins/curl_retriever.hpp b/resource_retriever/include/resource_retriever/plugins/curl_retriever.hpp new file mode 100644 index 0000000..5322c40 --- /dev/null +++ b/resource_retriever/include/resource_retriever/plugins/curl_retriever.hpp @@ -0,0 +1,68 @@ +// Copyright 2009, Willow Garage, Inc. All rights reserved. +// +// 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. +// +// * Neither the name of the Willow Garage, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// 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 COPYRIGHT HOLDER 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 RESOURCE_RETRIEVER__PLUGINS__CURL_RETRIEVER_HPP_ +#define RESOURCE_RETRIEVER__PLUGINS__CURL_RETRIEVER_HPP_ + +#include + +#include "resource_retriever/plugins/retriever_plugin.hpp" +#include "resource_retriever/visibility_control.hpp" + +using CURL = void; + +namespace resource_retriever::plugins +{ + +class RESOURCE_RETRIEVER_PUBLIC CurlRetriever : public RetrieverPlugin +{ +public: + /// Construct a CurlRetriever plugin and initialize libcurl. + /** + * \throws std::runtime_error if libcurl fails to initialize + */ + CurlRetriever(); + ~CurlRetriever() override; + + CurlRetriever(const CurlRetriever & ret) = delete; + CurlRetriever & operator=(const CurlRetriever & other) = delete; + + CurlRetriever(CurlRetriever && other) noexcept; + CurlRetriever & operator=(CurlRetriever && other) noexcept; + + bool can_handle(const std::string & url) override; + std::string name() override; + MemoryResourcePtr get(const std::string & url) override; + +private: + CURL * curl_handle_ {nullptr}; +}; + +} // namespace resource_retriever::plugins + +#endif // RESOURCE_RETRIEVER__PLUGINS__CURL_RETRIEVER_HPP_ diff --git a/resource_retriever/include/resource_retriever/plugins/filesystem_retriever.hpp b/resource_retriever/include/resource_retriever/plugins/filesystem_retriever.hpp new file mode 100644 index 0000000..ec424f9 --- /dev/null +++ b/resource_retriever/include/resource_retriever/plugins/filesystem_retriever.hpp @@ -0,0 +1,53 @@ +// Copyright 2009, Willow Garage, Inc. All rights reserved. +// +// 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. +// +// * Neither the name of the Willow Garage, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// 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 COPYRIGHT HOLDER 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 RESOURCE_RETRIEVER__PLUGINS__FILESYSTEM_RETRIEVER_HPP_ +#define RESOURCE_RETRIEVER__PLUGINS__FILESYSTEM_RETRIEVER_HPP_ + +#include + +#include "resource_retriever/plugins/retriever_plugin.hpp" +#include "resource_retriever/visibility_control.hpp" + +namespace resource_retriever::plugins +{ + +class RESOURCE_RETRIEVER_PUBLIC FilesystemRetriever : public RetrieverPlugin +{ +public: + FilesystemRetriever(); + ~FilesystemRetriever() override; + + bool can_handle(const std::string & url) override; + std::string name() override; + MemoryResourcePtr get(const std::string & url) override; +}; + +} // namespace resource_retriever::plugins + +#endif // RESOURCE_RETRIEVER__PLUGINS__FILESYSTEM_RETRIEVER_HPP_ diff --git a/resource_retriever/include/resource_retriever/plugins/retriever_plugin.hpp b/resource_retriever/include/resource_retriever/plugins/retriever_plugin.hpp new file mode 100644 index 0000000..e2b6967 --- /dev/null +++ b/resource_retriever/include/resource_retriever/plugins/retriever_plugin.hpp @@ -0,0 +1,55 @@ +// Copyright 2009, Willow Garage, Inc. All rights reserved. +// +// 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. +// +// * Neither the name of the Willow Garage, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// 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 COPYRIGHT HOLDER 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 RESOURCE_RETRIEVER__PLUGINS__RETRIEVER_PLUGIN_HPP_ +#define RESOURCE_RETRIEVER__PLUGINS__RETRIEVER_PLUGIN_HPP_ + +#include + +#include +#include "resource_retriever/visibility_control.hpp" + +namespace resource_retriever::plugins +{ +std::string expand_package_url(const std::string & url); +std::string escape_spaces(const std::string & url); + +class RESOURCE_RETRIEVER_PUBLIC RetrieverPlugin +{ +public: + RetrieverPlugin(); + virtual ~RetrieverPlugin() = 0; + + virtual std::string name() = 0; + virtual bool can_handle(const std::string & url) = 0; + virtual MemoryResourcePtr get(const std::string & url) = 0; +}; + +} // namespace resource_retriever::plugins + +#endif // RESOURCE_RETRIEVER__PLUGINS__RETRIEVER_PLUGIN_HPP_ diff --git a/resource_retriever/include/resource_retriever/retriever.hpp b/resource_retriever/include/resource_retriever/retriever.hpp index 2a1dcdc..cdeeb3d 100644 --- a/resource_retriever/include/resource_retriever/retriever.hpp +++ b/resource_retriever/include/resource_retriever/retriever.hpp @@ -33,30 +33,18 @@ #include #include #include +#include +#include "resource_retriever/plugins/retriever_plugin.hpp" #include "resource_retriever/visibility_control.hpp" -using CURL = void; - namespace resource_retriever { -class Exception : public std::runtime_error -{ -public: - Exception(const std::string & file, const std::string & error_msg) - : std::runtime_error("Error retrieving file [" + file + "]: " + error_msg) - { - } -}; -/** - * \brief A combination of a pointer to data in memory along with the data's size. - */ -struct MemoryResource -{ - std::shared_ptr data; - size_t size {0}; -}; +using RetrieverPluginPtr = std::shared_ptr; +using RetrieverVec = std::vector; + +RetrieverVec RESOURCE_RETRIEVER_PUBLIC default_plugins(); /** * \brief Retrieves files from from a url. Caches a CURL handle so multiple accesses to a single url @@ -65,26 +53,20 @@ struct MemoryResource class RESOURCE_RETRIEVER_PUBLIC Retriever { public: - Retriever(); + explicit Retriever(RetrieverVec plugins = default_plugins()); ~Retriever(); - Retriever(const Retriever & ret) = delete; - Retriever & operator=(const Retriever & other) = delete; - - Retriever(Retriever && other) noexcept; - Retriever & operator=(Retriever && other) noexcept; - /** * \brief Get a file and store it in memory * \param url The url to retrieve. package://package/file will be turned into the correct file:// invocation * \return The file, loaded into memory * \throws resource_retriever::Exception if anything goes wrong. */ - MemoryResource get(const std::string & url); + MemoryResourcePtr get(const std::string & url); private: - CURL * curl_handle_ {nullptr}; + RetrieverVec plugins; }; } // namespace resource_retriever diff --git a/resource_retriever/src/plugins/curl_retriever.cpp b/resource_retriever/src/plugins/curl_retriever.cpp new file mode 100644 index 0000000..a6c9f06 --- /dev/null +++ b/resource_retriever/src/plugins/curl_retriever.cpp @@ -0,0 +1,171 @@ +// Copyright 2009, Willow Garage, Inc. All rights reserved. +// +// 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. +// +// * Neither the name of the Willow Garage, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// 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 COPYRIGHT HOLDER 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. + +#include "resource_retriever/plugins/curl_retriever.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include "resource_retriever/exception.hpp" + +namespace +{ +class CURLStaticInit +{ +public: + CURLStaticInit() + { + ret_ = curl_global_init(CURL_GLOBAL_ALL); + if (ret_ == CURLE_OK) { + initialized_ = true; + } + } + + ~CURLStaticInit() + { + if (initialized_) { + curl_global_cleanup(); + } + } + + /// Check that libcurl is globally initialized, otherwise throw. + void check_if_initialized() const + { + if (!this->initialized_) { + throw std::runtime_error( + "curl_global_init(CURL_GLOBAL_ALL) failed (" + std::to_string(ret_) + "): " + + curl_easy_strerror(ret_)); + } + } + +private: + bool initialized_ {false}; + CURLcode ret_ = CURLE_OK; +}; +CURLStaticInit g_curl_init; +} // namespace + +namespace resource_retriever::plugins +{ + +struct MemoryBuffer +{ + std::vector v; +}; + +size_t curlWriteFunc(void * buffer, size_t size, size_t nmemb, void * userp) +{ + auto * membuf = reinterpret_cast(userp); + size_t prev_size = membuf->v.size(); + membuf->v.resize(prev_size + size * nmemb); + memcpy(&membuf->v[prev_size], buffer, size * nmemb); + return size * nmemb; +} + +CURL * do_curl_easy_init() +{ + ::g_curl_init.check_if_initialized(); + return curl_easy_init(); +} + +CurlRetriever::CurlRetriever() +: curl_handle_(do_curl_easy_init()) +{ +} + +CurlRetriever::~CurlRetriever() +{ + if (curl_handle_ != nullptr) { + curl_easy_cleanup(curl_handle_); + } +} + +CurlRetriever::CurlRetriever(CurlRetriever && other) noexcept +: curl_handle_(std::exchange(other.curl_handle_, nullptr)) +{ +} + +CurlRetriever & CurlRetriever::operator=(CurlRetriever && other) noexcept +{ + std::swap(curl_handle_, other.curl_handle_); + return *this; +} + +std::string CurlRetriever::name() +{ + return "resource_retriever::plugins::CurlRetriever"; +} + +bool CurlRetriever::can_handle(const std::string & url) +{ + return + url.find("package://") == 0 || + url.find("file://") == 0 || + url.find("http://") == 0 || + url.find("https://") == 0; +} + +MemoryResourcePtr CurlRetriever::get(const std::string & url) +{ + // Expand package:// url into file:// + auto mod_url = url; + try { + mod_url = expand_package_url(mod_url); + } catch (const resource_retriever::Exception & e) { + return nullptr; + } + + // newer versions of curl do not accept spaces in URLs + mod_url = escape_spaces(mod_url); + + curl_easy_setopt(curl_handle_, CURLOPT_URL, mod_url.c_str()); + curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION, curlWriteFunc); + + char error_buffer[CURL_ERROR_SIZE]; + curl_easy_setopt(curl_handle_, CURLOPT_ERRORBUFFER, error_buffer); + + MemoryResourcePtr res {nullptr}; + MemoryBuffer buf; + curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, &buf); + + CURLcode ret = curl_easy_perform(curl_handle_); + + if (ret == 0 && !buf.v.empty()) { + res = std::make_shared(url, mod_url, buf.v); + } + + return res; +} + +} // namespace resource_retriever::plugins diff --git a/resource_retriever/src/plugins/filesystem_retriever.cpp b/resource_retriever/src/plugins/filesystem_retriever.cpp new file mode 100644 index 0000000..07b09c4 --- /dev/null +++ b/resource_retriever/src/plugins/filesystem_retriever.cpp @@ -0,0 +1,88 @@ +// Copyright 2009, Willow Garage, Inc. All rights reserved. +// +// 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. +// +// * Neither the name of the Willow Garage, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// 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 COPYRIGHT HOLDER 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. + +#include "resource_retriever/plugins/filesystem_retriever.hpp" + +#include +#include +#include +#include + +#include "resource_retriever/exception.hpp" + +namespace resource_retriever::plugins +{ + +FilesystemRetriever::FilesystemRetriever() = default; + +FilesystemRetriever::~FilesystemRetriever() = default; + +std::string FilesystemRetriever::name() +{ + return "resource_retriever::plugins::FilesystemRetriever"; +} + +bool FilesystemRetriever::can_handle(const std::string & url) +{ + return url.find("package://") == 0 || url.find("file://") == 0; +} + +MemoryResourcePtr FilesystemRetriever::get(const std::string & url) +{ + // Expand package:// url into file:// + auto mod_url = url; + try { + mod_url = expand_package_url(mod_url); + } catch (const resource_retriever::Exception & e) { + return nullptr; + } + + if (mod_url.find("file://") == 0) { + mod_url = mod_url.substr(7); + } + + std::ifstream file(mod_url, std::ios::binary); + MemoryResourcePtr res {nullptr}; + + if (file.is_open()) { + // Get the file size + file.seekg(0, std::ios::end); + std::streampos fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + // Create the vector and read the file + std::vector data(fileSize); + file.read(reinterpret_cast(data.data()), fileSize); + file.close(); + res = std::make_shared(url, mod_url, data); + } + + return res; +} + +} // namespace resource_retriever::plugins diff --git a/resource_retriever/src/plugins/retriever_plugin.cpp b/resource_retriever/src/plugins/retriever_plugin.cpp new file mode 100644 index 0000000..fc31463 --- /dev/null +++ b/resource_retriever/src/plugins/retriever_plugin.cpp @@ -0,0 +1,95 @@ +// Copyright 2009, Willow Garage, Inc. All rights reserved. +// +// 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. +// +// * Neither the name of the Willow Garage, Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// 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 COPYRIGHT HOLDER 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. + +#include "resource_retriever/plugins/retriever_plugin.hpp" + +#include +#include + +#include "resource_retriever/exception.hpp" + +#include "ament_index_cpp/get_package_prefix.hpp" +#include "ament_index_cpp/get_package_share_directory.hpp" + +namespace resource_retriever::plugins +{ + +std::string escape_spaces(const std::string & url) +{ + std::string new_mod_url; + new_mod_url.reserve(url.length()); + + std::string::size_type last_pos = 0; + std::string::size_type find_pos; + + while (std::string::npos != (find_pos = url.find(' ', last_pos))) { + new_mod_url.append(url, last_pos, find_pos - last_pos); + new_mod_url += "%20"; + last_pos = find_pos + std::string(" ").length(); + } + + // Take care for the rest after last occurrence + new_mod_url.append(url, last_pos, url.length() - last_pos); + return new_mod_url; +} + +std::string expand_package_url(const std::string & url) +{ + constexpr std::string_view package_url_prefix = "package://"; + std::string mod_url = url; + if (url.find(package_url_prefix) == 0) { + mod_url.erase(0, package_url_prefix.length()); + size_t pos = mod_url.find('/'); + if (pos == std::string::npos) { + throw Exception( + url, + "Could not parse " + std::string(package_url_prefix) + " format into file:// format"); + } + + std::string package = mod_url.substr(0, pos); + if (package.empty()) { + throw Exception(url, "Package name must not be empty"); + } + mod_url.erase(0, pos); + std::string package_path; + try { + package_path = ament_index_cpp::get_package_share_directory(package); + } catch (const ament_index_cpp::PackageNotFoundError &) { + throw Exception(url, "Package [" + package + "] does not exist"); + } + + mod_url = "file://" + package_path + mod_url; + } + return mod_url; +} + +RetrieverPlugin::RetrieverPlugin() = default; + +RetrieverPlugin::~RetrieverPlugin() = default; + +} // namespace resource_retriever::plugins diff --git a/resource_retriever/src/retriever.cpp b/resource_retriever/src/retriever.cpp index 3ca5371..8e3d0f4 100644 --- a/resource_retriever/src/retriever.cpp +++ b/resource_retriever/src/retriever.cpp @@ -28,159 +28,45 @@ #include "resource_retriever/retriever.hpp" -#include - -#include #include #include #include -#include - -#include "ament_index_cpp/get_package_prefix.hpp" -#include "ament_index_cpp/get_package_share_directory.hpp" - - -namespace -{ -class CURLStaticInit -{ -public: - CURLStaticInit() - { - CURLcode ret = curl_global_init(CURL_GLOBAL_ALL); - if (ret != 0) { - fprintf(stderr, "Error initializing libcurl! retcode = %d", ret); - } else { - initialized_ = true; - } - } - - ~CURLStaticInit() - { - if (initialized_) { - curl_global_cleanup(); - } - } -private: - bool initialized_ {false}; -}; -CURLStaticInit g_curl_init; -} // namespace +#include "resource_retriever/exception.hpp" +#include "resource_retriever/plugins/curl_retriever.hpp" +#include "resource_retriever/plugins/filesystem_retriever.hpp" namespace resource_retriever { -Retriever::Retriever() -: curl_handle_(curl_easy_init()) +RetrieverVec default_plugins() { + return { + std::make_shared(), + std::make_shared(), + }; } -Retriever::~Retriever() -{ - if (curl_handle_ != nullptr) { - curl_easy_cleanup(curl_handle_); - } -} - -Retriever::Retriever(Retriever && other) noexcept -: curl_handle_(std::exchange(other.curl_handle_, nullptr)) +Retriever::Retriever(RetrieverVec plugins) +:plugins(std::move(plugins)) { } -Retriever & Retriever::operator=(Retriever && other) noexcept -{ - std::swap(curl_handle_, other.curl_handle_); - return *this; -} +Retriever::~Retriever() = default; -struct MemoryBuffer +MemoryResourcePtr Retriever::get(const std::string & url) { - std::vector v; -}; - -size_t curlWriteFunc(void * buffer, size_t size, size_t nmemb, void * userp) -{ - MemoryBuffer * membuf = reinterpret_cast(userp); - - size_t prev_size = membuf->v.size(); - membuf->v.resize(prev_size + size * nmemb); - memcpy(&membuf->v[prev_size], buffer, size * nmemb); - - return size * nmemb; -} + for (auto & plugin : plugins) { + if (plugin->can_handle(url)) { + auto res = plugin->get(url); -static std::string escape_spaces(const std::string & url) -{ - std::string new_mod_url; - new_mod_url.reserve(url.length()); - - std::string::size_type last_pos = 0; - std::string::size_type find_pos; - - while (std::string::npos != (find_pos = url.find(" ", last_pos))) { - new_mod_url.append(url, last_pos, find_pos - last_pos); - new_mod_url += "%20"; - last_pos = find_pos + std::string(" ").length(); - } - - // Take care for the rest after last occurrence - new_mod_url.append(url, last_pos, url.length() - last_pos); - return new_mod_url; -} - -MemoryResource Retriever::get(const std::string & url) -{ - std::string mod_url = url; - if (url.find("package://") == 0) { - mod_url.erase(0, strlen("package://")); - size_t pos = mod_url.find('/'); - if (pos == std::string::npos) { - throw Exception(url, "Could not parse package:// format into file:// format"); - } - - std::string package = mod_url.substr(0, pos); - if (package.empty()) { - throw Exception(url, "Package name must not be empty"); - } - mod_url.erase(0, pos); - std::string package_path; - try { - package_path = ament_index_cpp::get_package_share_directory(package); - } catch (const ament_index_cpp::PackageNotFoundError &) { - throw Exception(url, "Package [" + package + "] does not exist"); + if (res != nullptr) { + return res; + } } - - mod_url = "file://" + package_path + mod_url; - } - - // newer versions of curl do not accept spaces in URLs - mod_url = escape_spaces(mod_url); - - curl_easy_setopt(curl_handle_, CURLOPT_URL, mod_url.c_str()); - curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION, curlWriteFunc); - - char error_buffer[CURL_ERROR_SIZE]; - curl_easy_setopt(curl_handle_, CURLOPT_ERRORBUFFER, error_buffer); - - MemoryResource res; - MemoryBuffer buf; - curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, &buf); - - CURLcode ret = curl_easy_perform(curl_handle_); - if (ret != 0) { - throw Exception(mod_url, error_buffer); } - - if (!buf.v.empty()) { - res.size = buf.v.size(); - // Converted from boost::shared_array, see: https://stackoverflow.com/a/8624884 - res.data.reset(new uint8_t[res.size], std::default_delete()); - memcpy(res.data.get(), &buf.v[0], res.size); - } - - return res; + return nullptr; } } // namespace resource_retriever diff --git a/resource_retriever/test/test.cpp b/resource_retriever/test/test.cpp index 4f14aa2..3044dd8 100644 --- a/resource_retriever/test/test.cpp +++ b/resource_retriever/test/test.cpp @@ -26,21 +26,33 @@ // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. -#include #include +#include +#include +#include #include "gtest/gtest.h" +#include "resource_retriever/memory_resource.hpp" +#include "resource_retriever/plugins/retriever_plugin.hpp" #include "resource_retriever/retriever.hpp" +TEST(Retriever, defaultPlugins) +{ + auto plugins = resource_retriever::default_plugins(); + + EXPECT_EQ(plugins.size(), 2u); +} + TEST(Retriever, getByPackage) { try { resource_retriever::Retriever r; - resource_retriever::MemoryResource res = r.get("package://resource_retriever/test/test.txt"); + auto res = r.get("package://resource_retriever/test/test.txt"); - ASSERT_EQ(res.size, 1u); - ASSERT_EQ(*res.data, 'A'); + ASSERT_NE(nullptr, res); + ASSERT_EQ(res->data.size(), 1u); + ASSERT_EQ(*res->data.data(), 'A'); } catch (const std::exception & e) { FAIL() << "Exception caught: " << e.what() << '\n'; } @@ -50,9 +62,10 @@ TEST(Retriever, http) { try { resource_retriever::Retriever r; - resource_retriever::MemoryResource res = r.get("http://packages.ros.org/ros.key"); + auto res = r.get("http://packages.ros.org/ros.key"); - ASSERT_GT(res.size, 0u); + ASSERT_NE(nullptr, res); + ASSERT_GT(res->data.size(), 0u); } catch (const std::exception & e) { FAIL() << "Exception caught: " << e.what() << '\n'; } @@ -62,34 +75,50 @@ TEST(Retriever, invalidFiles) { resource_retriever::Retriever r; - try { - r.get("file://fail"); - FAIL(); - } catch (const std::exception & e) { - (void)e; - } + EXPECT_EQ(nullptr, r.get("file://fail")); + EXPECT_EQ(nullptr, r.get("package://roscpp")); + EXPECT_EQ(nullptr, r.get("package://invalid_package_blah/test.xml")); + EXPECT_EQ(nullptr, r.get("package:///test.xml")); +} - try { - r.get("package://roscpp"); - FAIL(); - } catch (const std::exception & e) { - (void)e; +class TestRetrieverPlugin : public resource_retriever::plugins::RetrieverPlugin +{ +public: + TestRetrieverPlugin() = default; + ~TestRetrieverPlugin() override = default; + + std::string name() override + { + return "TestRetrieverPlugin"; } - try { - r.get("package://invalid_package_blah/test.xml"); - FAIL(); - } catch (const std::exception & e) { - (void)e; + bool can_handle(const std::string & url) override + { + return url.find("test://") == 0; } - // Empty package name - try { - r.get("package:///test.xml"); - FAIL(); - } catch (const std::exception & e) { - (void)e; + resource_retriever::MemoryResourcePtr get(const std::string & url) override + { + return std::make_shared( + url, url, std::vector{0, 1, 2, 3, 4, 5}); } +}; + +TEST(Retriever, customPlugin) +{ + resource_retriever::RetrieverVec plugins { + std::make_shared() + }; + + resource_retriever::Retriever r(plugins); + EXPECT_EQ(nullptr, r.get("package://resource_retriever/test/text.txt")); + EXPECT_EQ(nullptr, r.get("file://foo/bar/text.txt")); + EXPECT_NE(nullptr, r.get("test://foo")); + + auto res = r.get("test://foo"); + EXPECT_EQ(res->data.size(), 6u); + EXPECT_EQ(res->data[0], 0u); + EXPECT_EQ(res->data[1], 1u); } int main(int argc, char ** argv)