22
33#include " net/curl_get.h"
44
5+ #include < cassert>
56#include < iostream>
67#include < curl/easy.h>
78
1112
1213namespace torrent ::net {
1314
15+ // TODO: Seperate the thread-owned and public variables in different cachelines.
16+
1417static size_t
1518curl_get_receive_write (void * data, size_t size, size_t nmemb, void * handle) {
1619 if (!((CurlGet*)handle)->stream ()->write ((const char *)data, size * nmemb).fail ())
@@ -19,70 +22,124 @@ curl_get_receive_write(void* data, size_t size, size_t nmemb, void* handle) {
1922 return 0 ;
2023}
2124
22- CurlGet::CurlGet (CurlStack* s)
23- : m_stack(s) {
24-
25+ CurlGet::CurlGet () {
2526 m_task_timeout.slot () = [this ]() { receive_timeout (); };
2627}
2728
2829CurlGet::~CurlGet () {
29- close ();
30+ // CurlStack keeps a shared_ptr to this object, so it will only be destroyed once it is removed
31+ // from the stack.
32+ assert (!is_busy () && " CurlGet::~CurlGet called while still busy." );
33+ }
34+
35+ void
36+ CurlGet::set_url (const std::string& url) {
37+ auto guard = lock_guard ();
38+
39+ if (m_handle != nullptr )
40+ throw torrent::internal_error (" CurlGet::set_url(...) called on a busy object." );
41+
42+ m_url = url;
3043}
3144
45+ // Make sure the output stream does not have any bad/failed bits set.
46+ //
47+ // TODO: Make the stream into something you pass to CurlGet, as a unique_ptr, and then have a way to
48+ // receive it in a thread-safe way on success.
3249void
33- CurlGet::start () {
34- if (is_busy ())
35- throw torrent::internal_error (" Tried to call CurlGet::start on a busy object." );
50+ CurlGet::set_stream (std::iostream* str) {
51+ auto guard = lock_guard ();
52+
53+ if (m_handle != nullptr )
54+ throw torrent::internal_error (" CurlGet::set_stream(...) called on a busy object." );
55+
56+ m_stream = str;
57+ }
58+
59+ void
60+ CurlGet::set_timeout (uint32_t seconds) {
61+ auto guard = lock_guard ();
62+
63+ if (m_handle != nullptr )
64+ throw torrent::internal_error (" CurlGet::set_timeout(...) called on a busy object." );
65+
66+ m_timeout = seconds;
67+ }
68+
69+ // TODO: When we add callback for start/close add an atomic_bool to indicate we've queued the
70+ // action, and use that to tell the user that the http_get is busy or not.
3671
37- if (m_stream == NULL )
38- throw torrent::internal_error (" Tried to call CurlGet::start without a valid output stream." );
72+ void
73+ CurlGet::prepare_start (CurlStack* stack) {
74+ if (m_handle != nullptr )
75+ throw torrent::internal_error (" CurlGet::prepare_start(...) called on a busy object." );
3976
40- if (!m_stack-> is_running () )
41- return ;
77+ if (m_stream == nullptr )
78+ throw torrent::internal_error ( " CurlGet::prepare_start(...) called with a null stream. " ) ;
4279
4380 m_handle = curl_easy_init ();
81+ m_stack = stack;
4482
45- if (m_handle == NULL )
83+ if (m_handle == nullptr )
4684 throw torrent::internal_error (" Call to curl_easy_init() failed." );
4785
4886 curl_easy_setopt (m_handle, CURLOPT_URL, m_url.c_str ());
4987 curl_easy_setopt (m_handle, CURLOPT_WRITEFUNCTION, &curl_get_receive_write);
5088 curl_easy_setopt (m_handle, CURLOPT_WRITEDATA, this );
5189
5290 if (m_timeout != 0 ) {
53- curl_easy_setopt (m_handle, CURLOPT_CONNECTTIMEOUT, (long )60 );
54- curl_easy_setopt (m_handle, CURLOPT_TIMEOUT, (long )m_timeout);
55-
56- // Normally libcurl should handle the timeout. But sometimes that doesn't
57- // work right so we do a fallback timeout that just aborts the transfer.
58- torrent::this_thread::scheduler ()->update_wait_for_ceil_seconds (&m_task_timeout, 5s + 1s*m_timeout);
91+ curl_easy_setopt (m_handle, CURLOPT_CONNECTTIMEOUT, 60l );
92+ curl_easy_setopt (m_handle, CURLOPT_TIMEOUT, static_cast <long >(m_timeout));
5993 }
6094
61- curl_easy_setopt (m_handle, CURLOPT_FORBID_REUSE, ( long ) 1 );
62- curl_easy_setopt (m_handle, CURLOPT_NOSIGNAL, ( long ) 1 );
63- curl_easy_setopt (m_handle, CURLOPT_FOLLOWLOCATION, ( long ) 1 );
64- curl_easy_setopt (m_handle, CURLOPT_MAXREDIRS, ( long ) 5 );
95+ curl_easy_setopt (m_handle, CURLOPT_FORBID_REUSE, 1l );
96+ curl_easy_setopt (m_handle, CURLOPT_NOSIGNAL, 1l );
97+ curl_easy_setopt (m_handle, CURLOPT_FOLLOWLOCATION, 1l );
98+ curl_easy_setopt (m_handle, CURLOPT_MAXREDIRS, 5l );
6599
66100 curl_easy_setopt (m_handle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);
67-
68101 curl_easy_setopt (m_handle, CURLOPT_ENCODING, " " );
69102
70103 m_ipv6 = false ;
104+ }
105+
106+ void
107+ CurlGet::activate () {
108+ CURLMcode code = curl_multi_add_handle (m_stack->handle (), m_handle);
109+
110+ if (code != CURLM_OK)
111+ throw torrent::internal_error (" CurlGet::activate() error calling curl_multi_add_handle: " + std::string (curl_multi_strerror (code)));
71112
72- m_stack->add_get (this );
113+ // Normally libcurl should handle the timeout. But sometimes that doesn't
114+ // work right so we do a fallback timeout that just aborts the transfer.
115+ //
116+ // TODO: Verify this is still needed, as it was added to work around during early libcurl
117+ // versions.
118+ if (m_timeout != 0 )
119+ torrent::this_thread::scheduler ()->update_wait_for_ceil_seconds (&m_task_timeout, 1min + 1s*m_timeout);
120+
121+ m_active = true ;
73122}
74123
75124void
76- CurlGet::close () {
77- torrent::this_thread::scheduler ()->erase (&m_task_timeout);
125+ CurlGet::cleanup () {
126+ if (m_handle == nullptr )
127+ throw torrent::internal_error (" CurlGet::cleanup() called on a null m_handle." );
78128
79- if (! is_busy ())
80- return ;
129+ if (m_active) {
130+ CURLMcode code = curl_multi_remove_handle (m_stack-> handle (), m_handle) ;
81131
82- m_stack->remove_get (this );
132+ if (code != CURLM_OK)
133+ throw torrent::internal_error (" CurlGet::cleanup() error calling curl_multi_remove_handle: " + std::string (curl_multi_strerror (code)));
134+
135+ torrent::this_thread::scheduler ()->erase (&m_task_timeout);
136+ m_active = false ;
137+ }
83138
84139 curl_easy_cleanup (m_handle);
85- m_handle = NULL ;
140+
141+ m_handle = nullptr ;
142+ m_stack = nullptr ;
86143}
87144
88145void
@@ -114,9 +171,12 @@ CurlGet::size_total() {
114171
115172void
116173CurlGet::receive_timeout () {
117- return m_stack->transfer_done (m_handle, " Timed out" );
174+ // return m_stack->transfer_done(m_handle, "Timed out");
175+ throw internal_error (" CurlGet::receive_timeout() called, however this was a hack to work around libcurl not handling timeouts correctly." );
118176}
119177
178+ // TODO: Verify slots are handled while CurlGet and CurlStack are unlocked.
179+
120180void
121181CurlGet::trigger_done () {
122182 ::utils::slot_list_call (m_signal_done);
0 commit comments