#define AL_LOG_SECTION "curl" #include #include "curl.h" // @TODO: // https://curl.se/libcurl/c/externalsocket.html // Use timer again/repeat. static void curl_socket_action_callback(struct ev_loop *loop, ev_io *w, s32 revents) { (void)loop; struct nn_curl *curl = (struct nn_curl *)w->data; s32 action = ((revents & EV_READ) ? CURL_CSELECT_IN : 0) | ((revents & EV_WRITE) ? CURL_CSELECT_OUT : 0); s32 running; CURLMcode mc = curl_multi_socket_action(curl->multi_handle, curl->sock, action, &running); if (mc != CURLM_OK) { log_error("curl_multi_socket_action() failed (%s).", curl_multi_strerror(mc)); return; } curl->handle_events(curl->userdata, curl); } // @TODO: ev_io_set static void set_sock(struct nn_curl *curl, s32 what) { if (curl->started) { ev_io_stop(curl->loop->ev, &curl->event); } s32 action = ((what & CURL_POLL_IN) ? EV_READ : 0) | ((what & CURL_POLL_OUT) ? EV_WRITE : 0); ev_io_init_n(&curl->event, curl_socket_action_callback, curl->sock, action); ev_io_start(curl->loop->ev, &curl->event); curl->started = true; } static s32 sock_callback(CURL *e, curl_socket_t s, s32 what, void *cbp, void *sockp) { (void)e; struct nn_curl *curl = (struct nn_curl *)cbp; if (what == CURL_POLL_REMOVE) { ev_io_stop(curl->loop->ev, &curl->event); curl_multi_assign(curl->multi_handle, curl->sock, NULL); curl->sock = 0; } else { if (!sockp) { curl->sock = s; curl_multi_assign(curl->multi_handle, curl->sock, curl); } set_sock(curl, what); } return 0; } static void timeout_callback(struct ev_loop *loop, ev_timer *w, s32 revents) { (void)loop; struct nn_curl *curl = (struct nn_curl *)w->data; (void)revents; if (curl->timer_started) { ev_timer_stop(curl->loop->ev, w); curl->timer_started = false; } s32 running; curl_multi_socket_action(curl->multi_handle, CURL_SOCKET_TIMEOUT, 0, &running); curl->handle_events(curl->userdata, curl); } static s32 timer_callback(CURLM *multi, s64 timeout_ms, void *userp) { (void)multi; struct nn_curl *curl = (struct nn_curl *)userp; if (curl->timer_started) { ev_timer_stop(curl->loop->ev, &curl->timer); curl->timer_started = false; } if (timeout_ms == -1) return 0; curl->timer.data = curl; ev_timer_init_n(&curl->timer, timeout_callback, timeout_ms / 1000.0, 0.0); curl->timer_started = true; ev_timer_start(curl->loop->ev, &curl->timer); return 0; } bool nn_curl_init(struct nn_curl *curl) { curl->handle = curl_easy_init(); if (!curl->handle) goto err; //curl_easy_setopt(curl->handle, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl->handle, CURLOPT_NOPROGRESS, 1L); curl->multi_handle = curl_multi_init(); if (!curl->multi_handle) goto err; curl_multi_setopt(curl->multi_handle, CURLMOPT_SOCKETFUNCTION, sock_callback); curl_multi_setopt(curl->multi_handle, CURLMOPT_SOCKETDATA, curl); curl_multi_setopt(curl->multi_handle, CURLMOPT_TIMERFUNCTION, timer_callback); curl_multi_setopt(curl->multi_handle, CURLMOPT_TIMERDATA, curl); curl->loop = NULL; curl->event.data = curl; curl->started = false; curl->timer_started = false; curl->added = false; return true; err: nn_curl_close(curl); return false; } void nn_curl_set_url(struct nn_curl *curl, str *url) { char *c_str = al_str_to_c_str(url); curl_easy_setopt(curl->handle, CURLOPT_URL, c_str); al_free(c_str); } bool nn_curl_add_handle(struct nn_curl *curl) { al_assert(!curl->added); // CURLM_RECURSIVE_API_CALL (8) also means we can't call this while // a callback is running on another thread. CURLMcode mc = curl_multi_add_handle(curl->multi_handle, curl->handle); if (mc != CURLM_OK) { log_error("curl_multi_add_handle() failed (%s).", curl_multi_strerror(mc)); return false; } curl->added = true; return true; } bool nn_curl_remove_handle(struct nn_curl *curl) { al_assert(curl->added); CURLMcode mc = curl_multi_remove_handle(curl->multi_handle, curl->handle); if (mc != CURLM_OK) { log_error("curl_multi_remove_handle() failed (%s).", curl_multi_strerror(mc)); return false; } curl->added = false; return true; } void nn_curl_close(struct nn_curl *curl) { if (curl->handle && curl->multi_handle && curl->added) { nn_curl_remove_handle(curl); } if (curl->handle) curl_easy_cleanup(curl->handle); if (curl->multi_handle) curl_multi_cleanup(curl->multi_handle); }