From 8f1dad571faf1d233690e4855b7890e9345d4719 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sun, 7 Jul 2019 14:49:54 +0700 Subject: [PATCH] add progress information to request #13 --- headers/curly.hpp/curly.hpp | 22 +++++++++ sources/curly.hpp/curly.cpp | 92 +++++++++++++++++++++++++++++++++++++ untests/curly_tests.cpp | 24 ++++++++++ 3 files changed, 138 insertions(+) diff --git a/headers/curly.hpp/curly.hpp b/headers/curly.hpp/curly.hpp index 834b939..acdd3f3 100644 --- a/headers/curly.hpp/curly.hpp +++ b/headers/curly.hpp/curly.hpp @@ -62,9 +62,18 @@ namespace curly_hpp virtual std::size_t write(const char* src, std::size_t size) = 0; }; + class progress_handler { + public: + virtual ~progress_handler() {} + virtual float update( + std::size_t dnow, std::size_t dtotal, + std::size_t unow, std::size_t utotal) = 0; + }; + using callback_t = std::function; using uploader_uptr = std::unique_ptr; using downloader_uptr = std::unique_ptr; + using progressor_uptr = std::unique_ptr; } namespace curly_hpp @@ -169,6 +178,7 @@ namespace curly_hpp headers_t headers; uploader_uptr uploader; downloader_uptr downloader; + progressor_uptr progressor; private: http_code_t http_code_{0u}; }; @@ -193,6 +203,7 @@ namespace curly_hpp request(internal_state_ptr); bool cancel() noexcept; + float progress() const noexcept; req_status status() const noexcept; bool is_done() const noexcept; @@ -246,6 +257,7 @@ namespace curly_hpp request_builder& callback(callback_t c) noexcept; request_builder& uploader(uploader_uptr u) noexcept; request_builder& downloader(downloader_uptr d) noexcept; + request_builder& progressor(progressor_uptr p) noexcept; const std::string& url() const noexcept; http_method method() const noexcept; @@ -270,6 +282,9 @@ namespace curly_hpp downloader_uptr& downloader() noexcept; const downloader_uptr& downloader() const noexcept; + progressor_uptr& progressor() noexcept; + const progressor_uptr& progressor() const noexcept; + request send(); template < typename Callback > @@ -289,6 +304,12 @@ namespace curly_hpp static_assert(std::is_base_of_v); return downloader(std::make_unique(std::forward(args)...)); } + + template < typename Progressor, typename... Args > + request_builder& progressor(Args&&... args) { + static_assert(std::is_base_of_v); + return progressor(std::make_unique(std::forward(args)...)); + } private: std::string url_; http_method method_{http_method::GET}; @@ -304,6 +325,7 @@ namespace curly_hpp callback_t callback_; uploader_uptr uploader_; downloader_uptr downloader_; + progressor_uptr progressor_; }; } diff --git a/sources/curly.hpp/curly.cpp b/sources/curly.hpp/curly.cpp index c33bf0e..389fcf3 100644 --- a/sources/curly.hpp/curly.cpp +++ b/sources/curly.hpp/curly.cpp @@ -77,6 +77,23 @@ namespace private: data_t& data_; }; + + class default_progressor final : public progress_handler { + public: + default_progressor() = default; + + float update( + std::size_t dnow, std::size_t dtotal, + std::size_t unow, std::size_t utotal) override + { + double now_d = static_cast(dnow + unow); + double total_d = static_cast(dtotal + utotal); + double progress_d = total_d > 0.0 + ? static_cast(now_d / total_d) + : 0.f; + return static_cast(std::min(std::max(progress_d, 0.0), 1.0)); + } + }; } // ----------------------------------------------------------------------------- @@ -253,6 +270,10 @@ namespace curly_hpp if ( !breq_.downloader() ) { breq_.downloader(&response_content_); } + + if ( !breq_.progressor() ) { + breq_.progressor(); + } } void enqueue(CURLM* curlm) { @@ -287,6 +308,10 @@ namespace curly_hpp curl_easy_setopt(curlh_.get(), CURLOPT_WRITEDATA, this); curl_easy_setopt(curlh_.get(), CURLOPT_WRITEFUNCTION, &s_download_callback_); + curl_easy_setopt(curlh_.get(), CURLOPT_NOPROGRESS, 0l); + curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFODATA, this); + curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFOFUNCTION, &s_progress_callback_); + curl_easy_setopt(curlh_.get(), CURLOPT_HEADERDATA, this); curl_easy_setopt(curlh_.get(), CURLOPT_HEADERFUNCTION, &s_header_callback_); @@ -364,6 +389,21 @@ namespace curly_hpp void dequeue(CURLM* curlm) noexcept { std::lock_guard guard(mutex_); if ( curlh_ ) { + curl_easy_setopt(curlh_.get(), CURLOPT_PRIVATE, nullptr); + + curl_easy_setopt(curlh_.get(), CURLOPT_READDATA, nullptr); + curl_easy_setopt(curlh_.get(), CURLOPT_READFUNCTION, nullptr); + + curl_easy_setopt(curlh_.get(), CURLOPT_WRITEDATA, nullptr); + curl_easy_setopt(curlh_.get(), CURLOPT_WRITEFUNCTION, nullptr); + + curl_easy_setopt(curlh_.get(), CURLOPT_NOPROGRESS, 1l); + curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFODATA, nullptr); + curl_easy_setopt(curlh_.get(), CURLOPT_XFERINFOFUNCTION, nullptr); + + curl_easy_setopt(curlh_.get(), CURLOPT_HEADERDATA, nullptr); + curl_easy_setopt(curlh_.get(), CURLOPT_HEADERFUNCTION, nullptr); + curl_multi_remove_handle(curlm, curlh_.get()); curlh_.reset(); } @@ -392,12 +432,14 @@ namespace curly_hpp response_.headers = std::move(response_headers_); response_.uploader = std::move(breq_.uploader()); response_.downloader = std::move(breq_.downloader()); + response_.progressor = std::move(breq_.progressor()); } catch (...) { status_ = req_status::failed; cvar_.notify_all(); return false; } + progress_ = 1.f; status_ = req_status::done; error_.clear(); @@ -448,6 +490,11 @@ namespace curly_hpp return true; } + float progress() const noexcept { + std::lock_guard guard(mutex_); + return progress_; + } + req_status status() const noexcept { std::lock_guard guard(mutex_); return status_; @@ -553,6 +600,13 @@ namespace curly_hpp return self->download_callback_(ptr, size * nmemb); } + static int s_progress_callback_( + void* clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) noexcept + { + auto* self = static_cast(clientp); + return self->progress_callback(dlnow, dltotal, ulnow, ultotal); + } + static std::size_t s_header_callback_( char* buffer, std::size_t size, std::size_t nitems, void* userdata) noexcept { @@ -589,6 +643,26 @@ namespace curly_hpp } } + int progress_callback( + curl_off_t dlnow, curl_off_t dltotal, + curl_off_t ulnow, curl_off_t ultotal) noexcept + { + try { + std::lock_guard guard(mutex_); + + std::size_t dnow_sz = dlnow > 0 ? static_cast(dlnow) : 0u; + std::size_t dtotal_sz = dltotal > 0 ? static_cast(dltotal) : 0u; + + std::size_t unow_sz = ulnow > 0 ? static_cast(ulnow) : 0u; + std::size_t utotal_sz = ultotal > 0 ? static_cast(ultotal) : 0u; + + progress_ = breq_.progressor()->update(dnow_sz, dtotal_sz, unow_sz, utotal_sz); + return 0; + } catch (...) { + return 1; + } + } + std::size_t header_callback_(const char* src, std::size_t size) noexcept { try { std::lock_guard guard(mutex_); @@ -631,6 +705,7 @@ namespace curly_hpp bool callbacked_{false}; std::exception_ptr callback_exception_{nullptr}; private: + float progress_{0.f}; req_status status_{req_status::pending}; std::string error_{"Unknown error"}; private: @@ -650,6 +725,10 @@ namespace curly_hpp return state_->cancel(); } + float request::progress() const noexcept { + return state_->progress(); + } + req_status request::status() const noexcept { return state_->status(); } @@ -787,6 +866,11 @@ namespace curly_hpp return *this; } + request_builder& request_builder::progressor(progressor_uptr p) noexcept { + progressor_ = std::move(p); + return *this; + } + const std::string& request_builder::url() const noexcept { return url_; } @@ -855,6 +939,14 @@ namespace curly_hpp return downloader_; } + progressor_uptr& request_builder::progressor() noexcept { + return progressor_; + } + + const progressor_uptr& request_builder::progressor() const noexcept { + return progressor_; + } + request request_builder::send() { auto sreq = std::make_shared(std::move(*this)); new_handles.enqueue(sreq); diff --git a/untests/curly_tests.cpp b/untests/curly_tests.cpp index 1fef52c..e90c5cd 100644 --- a/untests/curly_tests.cpp +++ b/untests/curly_tests.cpp @@ -58,6 +58,22 @@ namespace } }; + class canceled_progressor : public net::progress_handler { + public: + canceled_progressor() = default; + + float update( + std::size_t dnow, std::size_t dtotal, + std::size_t unow, std::size_t utotal) override + { + (void)dnow; + (void)dtotal; + (void)unow; + (void)utotal; + throw std::exception(); + } + }; + netex::promise download(std::string url) { return netex::make_promise([ url = std::move(url) @@ -673,6 +689,14 @@ TEST_CASE("curly") { .send(); REQUIRE(req.wait() == net::req_status::canceled); } + { + auto req = net::request_builder("https://httpbin.org/anything") + .verbose(true) + .method(net::http_method::GET) + .progressor() + .send(); + REQUIRE(req.wait() == net::req_status::canceled); + } } SECTION("callback") {