diff --git a/CMakeLists.txt b/CMakeLists.txt index 68d6ca6..cbd24f9 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,7 @@ set(sources "src/main.cpp" "include/Helpers/Logger.cpp" "include/Helpers/StringHelpers.cpp" "include/Helpers/Serializers.cpp" + "include/Helpers/FileDownloader.cpp" ) if((NOT EMSCRIPTEN) AND (GRAPHICS_BACKEND STREQUAL "Vulkan")) @@ -144,6 +145,7 @@ endif() if(NOT EMSCRIPTEN) find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-shared) find_package(LibDataChannel REQUIRED) + find_package(CURL REQUIRED) else() find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3-static) endif() @@ -266,7 +268,7 @@ if(EMSCRIPTEN) add_subdirectory(deps/datachannel-wasm EXCLUDE_FROM_ALL) target_link_libraries(infinipaint datachannel-wasm) else() - target_link_libraries(infinipaint LibDataChannel::LibDataChannel) + target_link_libraries(infinipaint LibDataChannel::LibDataChannel CURL::libcurl) endif() if(LINK_SKIA_MANUALLY) diff --git a/conan/export_libs.sh b/conan/export_libs.sh index a4ea045..6e3b07e 100755 --- a/conan/export_libs.sh +++ b/conan/export_libs.sh @@ -3,7 +3,7 @@ SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) conan export "$(dirname "$0")/libjuice/all/conanfile.py" --version=1.6.1 -conan export "$(dirname "$0")/libdatachannel/all/conanfile.py" --version=0.23.1 +conan export "$(dirname "$0")/libdatachannel/all/conanfile.py" --version=0.23.2 conan export "$(dirname "$0")/mpg123/all/conanfile.py" --version=1.32.10 conan export "$(dirname "$0")/conan-skia/recipes/skia/all/conanfile.py" --version=134.20250327.0 #conan create . -o use_system_expat=False -o use_system_freetype=False -o use_system_harfbuzz=False -o use_system_icu=False -o use_system_libjpeg_turbo=False -o use_system_libpng=False -o use_system_libwebp=False -o use_system_zlib=False --build=missing --version=134.20250327.0 diff --git a/conan/libdatachannel/all/conandata.yml b/conan/libdatachannel/all/conandata.yml index bf37175..1052f32 100644 --- a/conan/libdatachannel/all/conandata.yml +++ b/conan/libdatachannel/all/conandata.yml @@ -1,4 +1,7 @@ sources: + "0.23.2": + url: "https://github.com/paullouisageneau/libdatachannel/archive/refs/tags/v0.23.2.tar.gz" + sha256: "b9606efc5b2b173f2d22d0be3f6ba4f12af78c00ca02cde5932f3ff902980eb9" "0.23.1": url: "https://github.com/paullouisageneau/libdatachannel/archive/refs/tags/v0.23.1.tar.gz" sha256: "63e14d619ac4d9cc310a0c7620b80e6da88abf878f27ccc78cd099f95d47b121" diff --git a/conanfile.py b/conanfile.py index e7ee71a..1cf65d5 100644 --- a/conanfile.py +++ b/conanfile.py @@ -51,7 +51,8 @@ class CompressorRecipe(ConanFile): self.requires("onetbb/2022.0.0") if self.settings.os != "Emscripten": - self.requires("libdatachannel/0.23.1") + self.requires("libdatachannel/0.23.2") + self.requires("libcurl/8.15.0") self.requires("zstd/1.5.7") diff --git a/data/third_party_licenses/libcurl b/data/third_party_licenses/libcurl new file mode 100644 index 0000000..3fa85eb --- /dev/null +++ b/data/third_party_licenses/libcurl @@ -0,0 +1,22 @@ +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1996 - 2025, Daniel Stenberg, , and many +contributors, see the THANKS file. + +All rights reserved. + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization of the copyright holder. diff --git a/include/Helpers/FileDownloader.cpp b/include/Helpers/FileDownloader.cpp new file mode 100644 index 0000000..916eafd --- /dev/null +++ b/include/Helpers/FileDownloader.cpp @@ -0,0 +1,135 @@ +#include "FileDownloader.hpp" +#ifndef __EMSCRIPTEN__ + +#include +#include +#include + +std::mutex FileDownloader::newDownloadsMutex; +std::vector FileDownloader::newDownloads; +std::atomic FileDownloader::shutdownThread; +FileDownloader::MultiHandle FileDownloader::mHandle; +std::vector FileDownloader::currentDownloads; +std::unique_ptr FileDownloader::downloadThread; + +void FileDownloader::init() { + curl_global_init(CURL_GLOBAL_DEFAULT); + shutdownThread = false; +} + +std::shared_ptr FileDownloader::download_data_from_url(std::string_view url) { + DownloadHandler h; + auto downloadData = std::make_shared(); + h.data = downloadData; + h.data->status = DownloadData::Status::IN_PROGRESS; + h.eHandle = create_easy_handle(); + curl_easy_setopt(h.eHandle.get(), CURLOPT_URL, url.data()); + disable_ssl_verification(h.eHandle.get()); + save_to_string(h.eHandle.get(), &h.data->str); + + { + std::scoped_lock newDownloadsLock(newDownloadsMutex); + newDownloads.emplace_back(std::move(h)); + } + + if(!downloadThread) + downloadThread = std::make_unique(download_thread_update); + + return downloadData; +} + +void FileDownloader::add_new_handlers_to_multi() { + std::scoped_lock newDownloadsLock(newDownloadsMutex); + for(auto& handler : newDownloads) { + curl_multi_add_handle(mHandle.get(), handler.eHandle.get()); + currentDownloads.emplace_back(std::move(handler)); + } + newDownloads.clear(); +} + +void FileDownloader::download_thread_update() { + int stillRunning = 0; + + mHandle = create_multi_handle(); + add_new_handlers_to_multi(); + + do { + if(shutdownThread) + break; + + CURLMcode mc; + int numfds; + + mc = curl_multi_perform(mHandle.get(), &stillRunning); + + for(;;) { + int msgCount; + CURLMsg* eHandleMsg = curl_multi_info_read(mHandle.get(), &msgCount); + if(!eHandleMsg) + break; + if(eHandleMsg->msg == CURLMSG_DONE) { + auto it = std::find_if(currentDownloads.begin(), currentDownloads.end(), [eHandleMsg](auto& d) { + return eHandleMsg->easy_handle == d.eHandle.get(); + }); + if(it != currentDownloads.end()) { + DownloadHandler& download = *it; + download.data->status = eHandleMsg->data.result == CURLE_OK ? DownloadData::Status::SUCCESS : DownloadData::Status::FAILURE; + currentDownloads.erase(it); + } + } + } + + if(mc == CURLM_OK) + curl_multi_poll(mHandle.get(), nullptr, 0, 1000, &numfds); + else { + std::cout << "curl_multi failed" << std::endl; + break; + } + } while(stillRunning); + + for(auto& download : currentDownloads) + download.data->status = DownloadData::Status::FAILURE; + currentDownloads.clear(); +} + +void FileDownloader::save_to_string(CURL* curl, std::string* str) { + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_to_string); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, reinterpret_cast(str)); +} + +void FileDownloader::disable_ssl_verification(CURL* curl) { + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0); +} + +FileDownloader::EasyHandle FileDownloader::create_easy_handle() { + auto easyHandle = EasyHandle(curl_easy_init(), curl_easy_cleanup); + if(!easyHandle) + throw std::runtime_error("[FileDownloader::create_easy_handle] Failed to create CURL easy handle"); + return easyHandle; +} + +FileDownloader::MultiHandle FileDownloader::create_multi_handle() { + auto multiHandle = EasyHandle(curl_multi_init(), curl_multi_cleanup); + if(!multiHandle) + throw std::runtime_error("[FileDownloader::create_multi_handle] Failed to create CURL multi handle"); + return multiHandle; +} + +size_t FileDownloader::write_to_string(void* contents, size_t size, size_t nmemb, void* userp) { + size_t realsize = size * nmemb; + auto str = reinterpret_cast(userp); + str->append(reinterpret_cast(contents), realsize); + return realsize; +} + +void FileDownloader::cleanup() { + if(downloadThread && downloadThread->joinable()) { + shutdownThread = true; + downloadThread->join(); + } + + curl_global_cleanup(); +} + +#endif diff --git a/include/Helpers/FileDownloader.hpp b/include/Helpers/FileDownloader.hpp new file mode 100644 index 0000000..06f0c96 --- /dev/null +++ b/include/Helpers/FileDownloader.hpp @@ -0,0 +1,57 @@ +#pragma once +#ifndef __EMSCRIPTEN__ + +#include +#include +#include +#include +#include +#include + +class FileDownloader { + public: + struct DownloadData { + enum class Status { + IN_PROGRESS = 0, + SUCCESS, + FAILURE + }; + std::string str; + std::atomic status; + }; + + static void init(); + static std::shared_ptr download_data_from_url(std::string_view url); + static void cleanup(); + private: + typedef std::unique_ptr> EasyHandle; + typedef std::unique_ptr> MultiHandle; + + static void add_new_handlers_to_multi(); + static void download_thread_update(); + + struct DownloadHandler { + EasyHandle eHandle; + std::shared_ptr data; + }; + + static std::mutex newDownloadsMutex; + static std::vector newDownloads; + static std::atomic shutdownThread; + static MultiHandle mHandle; + static std::vector currentDownloads; + static std::unique_ptr downloadThread; + + static void save_to_string(CURL* curl, std::string* str); + static void disable_ssl_verification(CURL* curl); + + static timeval get_timeout(CURLM* mCurl); + static int wait_if_needed(CURLM* mCurl, timeval& timeout); + + static EasyHandle create_easy_handle(); + static MultiHandle create_multi_handle(); + + static size_t write_to_string(void* contents, size_t size, size_t nmemb, void* userp); +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp index 17acd1b..e9b645e 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,6 @@ #include "Helpers/SCollision.hpp" #include "Helpers/StringHelpers.hpp" +#include "Helpers/FileDownloader.hpp" #include "include/gpu/GpuTypes.h" #include "sago/platform_folders.h" #include @@ -387,6 +388,10 @@ SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) { init_logs(mS); +#ifndef __EMSCRIPTEN__ + FileDownloader::init(); +#endif + #ifdef NDEBUG try { #endif @@ -770,5 +775,9 @@ void SDL_AppQuit(void *appstate, SDL_AppResult result) { delete (&mS); +#ifndef __EMSCRIPTEN__ + FileDownloader::cleanup(); +#endif + SDL_Quit(); }