aboutsummaryrefslogtreecommitdiffstats
path: root/src/downloadpool.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/downloadpool.c')
-rw-r--r--src/downloadpool.c177
1 files changed, 177 insertions, 0 deletions
diff --git a/src/downloadpool.c b/src/downloadpool.c
new file mode 100644
index 0000000..5da2905
--- /dev/null
+++ b/src/downloadpool.c
@@ -0,0 +1,177 @@
+#include "downloadpool.h"
+#include "macros.h"
+#include <curl/curl.h>
+#include <curl/multi.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+struct l2_dlpool__handle {
+ CURL *easy;
+ void *user;
+
+ struct l2_dlpool__handle *next;
+ struct l2_dlpool__handle *prev;
+};
+
+struct l2_dlpool__pool_tag {
+ CURLM *multi;
+
+ struct l2_dlpool__handle *head, *tail;
+
+ l2_dlpool_download_init_proc_t *initproc;
+ l2_dlpool_one_downloaded_proc_t *dlproc;
+};
+
+l2_dlpool_t *l2_dlpool_init(l2_dlpool_download_init_proc_t *init, l2_dlpool_one_downloaded_proc_t *dlproc)
+{
+ l2_dlpool_t *ret = calloc(1, sizeof(l2_dlpool_t));
+ if (!ret) return NULL;
+
+ ret->multi = curl_multi_init();
+ if (!ret->multi) {
+ goto cleanup;
+ }
+
+ ret->initproc = init;
+ ret->dlproc = dlproc;
+
+ return ret;
+
+cleanup:
+ if (ret) {
+ if (ret->multi) curl_multi_cleanup(ret->multi);
+ }
+ free(ret);
+ return NULL;
+}
+
+int l2_dlpool_add(l2_dlpool_t *pool, void *user)
+{
+ int res = -1;
+ struct l2_dlpool__handle *newhandle = calloc(1, sizeof(struct l2_dlpool__handle));
+ if (!newhandle) return -1;
+
+ newhandle->easy = curl_easy_init();
+ if (!newhandle->easy) goto cleanup;
+
+ curl_easy_setopt(newhandle->easy, CURLOPT_USERAGENT, L2_USER_AGENT);
+
+ if ((*pool->initproc)(newhandle->easy, user) < 0) {
+ res = 0;
+ goto cleanup;
+ }
+
+ newhandle->user = user;
+
+ CURLMcode mc = curl_multi_add_handle(pool->multi, newhandle->easy);
+ if (mc != CURLM_OK) {
+ if (mc == CURLM_OUT_OF_MEMORY) {
+ fputs("CURL out of memory. (You are doomed.)\n", stderr);
+ abort();
+ }
+ goto cleanup;
+ }
+
+ if (pool->tail) {
+ pool->tail->next = newhandle;
+ newhandle->prev = pool->tail;
+ pool->tail = newhandle;
+ } else {
+ pool->head = pool->tail = newhandle;
+ }
+
+ return 1;
+
+cleanup:
+ if (newhandle) {
+ if (newhandle->easy) curl_easy_cleanup(newhandle->easy);
+ }
+ free(newhandle);
+ return res;
+}
+
+int l2_dlpool__download_complete(l2_dlpool_t *pool, CURL *easy, CURLcode code)
+{
+ struct l2_dlpool__handle *handle = NULL;
+ int uret = 0;
+ for (struct l2_dlpool__handle *cur = pool->head; cur; cur = cur->next) {
+ if (cur->easy == easy) {
+ handle = cur;
+ continue;
+ }
+ }
+
+ if (handle) {
+ uret = (*pool->dlproc)(easy, code, handle->user);
+
+ if (handle->prev) {
+ handle->prev->next = handle->next;
+ } else {
+ pool->head = handle->next;
+ }
+
+ if (handle->next) {
+ handle->next->prev = handle->prev;
+ } else {
+ pool->tail = handle->prev;
+ }
+
+ free(handle);
+ handle = NULL;
+ }
+
+ curl_multi_remove_handle(pool->multi, easy);
+ curl_easy_cleanup(easy);
+
+ if (uret < 0) return -1;
+ return 0;
+}
+
+int l2_dlpool_perform(l2_dlpool_t *pool)
+{
+ CURLMcode code;
+ int running = 0;
+ int queuelen = 0;
+ CURLMsg *msg;
+
+ do {
+ code = curl_multi_perform(pool->multi, &running);
+ if (code != CURLM_OK) {
+ return -1;
+ }
+
+ while ((msg = curl_multi_info_read(pool->multi, &queuelen))) {
+ if (msg->msg == CURLMSG_DONE) {
+ if (l2_dlpool__download_complete(pool, msg->easy_handle, msg->data.result) < 0) return -1;
+ }
+ }
+
+ code = curl_multi_poll(pool->multi, NULL, 0, 1000, NULL);
+ if (code != CURLM_OK) {
+ return -1;
+ }
+ } while (running);
+
+ return 0;
+}
+
+void l2_dlpool_cleanup(l2_dlpool_t *pool)
+{
+ if (!pool) return;
+
+ /* assumes pool->multi is valid (API must not return a handle where this isn't the case) */
+
+ /* curl docs say to remove handles BEFORE ANY HANDLES ARE CLEANED UP */
+ for (struct l2_dlpool__handle *cur = pool->head; cur; cur = cur->next) {
+ curl_multi_remove_handle(pool->multi, cur->easy); /* assume it succeeds (if it fails, too bad we tried LOL) */
+ }
+
+ for (struct l2_dlpool__handle *cur = pool->head, *temp; cur; cur = temp) {
+ temp = cur->next;
+ curl_easy_cleanup(cur->easy);
+ free(cur);
+ }
+
+ curl_multi_cleanup(pool->multi);
+ free(pool);
+}