1 /*#############################################################################
3 # Pakfire - The IPFire package management system #
4 # Copyright (C) 2021 Pakfire development team #
6 # This program is free software: you can redistribute it and/or modify #
7 # it under the terms of the GNU General Public License as published by #
8 # the Free Software Foundation, either version 3 of the License, or #
9 # (at your option) any later version. #
11 # This program is distributed in the hope that it will be useful, #
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
14 # GNU General Public License for more details. #
16 # You should have received a copy of the GNU General Public License #
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 #############################################################################*/
23 #include <sys/queue.h>
25 #include <curl/curl.h>
27 #include <pakfire/httpclient.h>
28 #include <pakfire/logging.h>
29 #include <pakfire/progress.h>
30 #include <pakfire/xfer.h>
32 // The number of concurrent downloads
33 #define MAX_PARALLEL 4
35 struct pakfire_xfer_element
{
36 TAILQ_ENTRY(pakfire_xfer_element
) nodes
;
38 struct pakfire_xfer
* xfer
;
41 struct pakfire_httpclient
{
42 struct pakfire_ctx
* ctx
;
45 unsigned int parallel
;
54 TAILQ_HEAD(xfers_queued
, pakfire_xfer_element
) xfers_queued
;
55 TAILQ_HEAD(xfers_running
, pakfire_xfer_element
) xfers_running
;
58 static int pakfire_httpclient_setup_curl(struct pakfire_httpclient
* client
) {
62 r
= curl_global_init(CURL_GLOBAL_DEFAULT
);
64 CTX_ERROR(client
->ctx
, "Could not initialize cURL: %d\n", r
);
68 // Create a new share handle
69 client
->share
= curl_share_init();
71 CTX_ERROR(client
->ctx
, "Could not setup cURL share handle\n");
75 // Create a new multi handle
76 client
->curl
= curl_multi_init();
78 CTX_ERROR(client
->ctx
, "Could not create cURL multi handle\n");
82 // Share connections between handles
83 r
= curl_share_setopt(client
->share
, CURLSHOPT_SHARE
, CURL_LOCK_DATA_CONNECT
);
85 CTX_ERROR(client
->ctx
, "Could not configure the share handle: %s\n",
86 curl_share_strerror(r
));
93 static struct pakfire_xfer_element
* pakfire_httpclient_xfer_create(struct pakfire_xfer
* xfer
) {
94 struct pakfire_xfer_element
* x
= NULL
;
96 // Allocate a new element
97 x
= calloc(1, sizeof(*x
));
101 // Store a reference to the xfer
102 x
->xfer
= pakfire_xfer_ref(xfer
);
107 static struct pakfire_xfer_element
* pakfire_httpclient_xfer_find_running(
108 struct pakfire_httpclient
* client
, struct pakfire_xfer
* xfer
) {
109 struct pakfire_xfer_element
* x
= NULL
;
111 TAILQ_FOREACH(x
, &client
->xfers_running
, nodes
) {
119 static void pakfire_httpclient_xfer_free(struct pakfire_xfer_element
* x
) {
121 pakfire_xfer_unref(x
->xfer
);
126 static void pakfire_httpclient_free(struct pakfire_httpclient
* client
) {
127 struct pakfire_xfer_element
* x
= NULL
;
129 // Free any queued transfers
130 while (!TAILQ_EMPTY(&client
->xfers_queued
)) {
131 x
= TAILQ_LAST(&client
->xfers_queued
, xfers_queued
);
132 TAILQ_REMOVE(&client
->xfers_queued
, x
, nodes
);
134 pakfire_httpclient_xfer_free(x
);
137 // Free any running transfers
138 while (!TAILQ_EMPTY(&client
->xfers_running
)) {
139 x
= TAILQ_LAST(&client
->xfers_running
, xfers_running
);
140 TAILQ_REMOVE(&client
->xfers_running
, x
, nodes
);
142 pakfire_httpclient_xfer_free(x
);
146 curl_share_cleanup(client
->share
);
148 curl_multi_cleanup(client
->curl
);
150 pakfire_ctx_unref(client
->ctx
);
154 int pakfire_httpclient_create(struct pakfire_httpclient
** client
, struct pakfire_ctx
* ctx
) {
155 struct pakfire_httpclient
* c
= NULL
;
158 // Fail if the context is flagged as offline
159 if (pakfire_ctx_has_flag(ctx
, PAKFIRE_CTX_OFFLINE
)) {
160 CTX_ERROR(ctx
, "Cannot initialize downloader in offline mode\n");
164 // Allocate a new object
165 c
= calloc(1, sizeof(*c
));
169 // Store reference to the context
170 c
->ctx
= pakfire_ctx_ref(ctx
);
172 // Initialize reference counting
176 c
->parallel
= MAX_PARALLEL
;
178 // Init transfer queues
179 TAILQ_INIT(&c
->xfers_queued
);
180 TAILQ_INIT(&c
->xfers_running
);
183 r
= pakfire_httpclient_setup_curl(c
);
193 pakfire_httpclient_free(c
);
198 struct pakfire_httpclient
* pakfire_httpclient_ref(struct pakfire_httpclient
* client
) {
204 struct pakfire_httpclient
* pakfire_httpclient_unref(struct pakfire_httpclient
* client
) {
205 if (--client
->nrefs
> 0)
208 pakfire_httpclient_free(client
);
212 CURLSH
* pakfire_httpclient_share(struct pakfire_httpclient
* client
) {
213 return client
->share
;
216 int pakfire_httpclient_create_xfer(struct pakfire_xfer
** xfer
,
217 struct pakfire_httpclient
* client
, const char* url
) {
218 return pakfire_xfer_create(xfer
, client
->ctx
, client
, url
);
221 int pakfire_httpclient_enqueue_xfer(struct pakfire_httpclient
* client
,
222 struct pakfire_xfer
* xfer
) {
223 struct pakfire_xfer_element
* x
= NULL
;
225 // Create a new queueable object
226 x
= pakfire_httpclient_xfer_create(xfer
);
230 // Push this transfer onto the queue
231 TAILQ_INSERT_HEAD(&client
->xfers_queued
, x
, nodes
);
236 static size_t pakfire_httpclient_total_downloadsize(struct pakfire_httpclient
* client
) {
237 struct pakfire_xfer_element
* x
= NULL
;
240 size_t total_size
= 0;
242 TAILQ_FOREACH(x
, &client
->xfers_queued
, nodes
) {
243 size
= pakfire_xfer_get_size(x
->xfer
);
245 // Return zero if the size isn't known
255 static unsigned int pakfire_httpclient_total_queued_xfers(struct pakfire_httpclient
* client
) {
256 struct pakfire_xfer_element
* x
= NULL
;
257 unsigned int counter
= 0;
259 TAILQ_FOREACH(x
, &client
->xfers_queued
, nodes
)
265 static int pakfire_httpclient_start_transfers(struct pakfire_httpclient
* client
,
266 struct pakfire_progress
* progress
, unsigned int* running_transfers
) {
267 struct pakfire_xfer_element
* x
= NULL
;
270 // Keep running until we have reached our ceiling
271 while (*running_transfers
< client
->parallel
) {
272 // We are done if there are no more transfers in the queue
273 if (TAILQ_EMPTY(&client
->xfers_queued
))
276 // Fetch the next transfer
277 x
= TAILQ_LAST(&client
->xfers_queued
, xfers_queued
);
278 TAILQ_REMOVE(&client
->xfers_queued
, x
, nodes
);
281 r
= pakfire_xfer_prepare(x
->xfer
, progress
, 0);
285 // Add the handle to cURL
286 r
= curl_multi_add_handle(client
->curl
, pakfire_xfer_handle(x
->xfer
));
288 CTX_ERROR(client
->ctx
, "Adding handle failed: %s\n", curl_multi_strerror(r
));
292 TAILQ_INSERT_TAIL(&client
->xfers_running
, x
, nodes
);
293 (*running_transfers
)++;
299 pakfire_httpclient_xfer_free(x
);
304 int pakfire_httpclient_run(struct pakfire_httpclient
* client
, const char* title
) {
305 struct pakfire_progress
* progress
= NULL
;
306 struct pakfire_xfer
* xfer
= NULL
;
307 unsigned int running_xfers
= 0;
309 PAKFIRE_PROGRESS_SHOW_PERCENTAGE
|
310 PAKFIRE_PROGRESS_SHOW_ETA
;
313 struct pakfire_xfer_element
* x
= NULL
;
319 // Fetch the total downloadsize
320 const size_t downloadsize
= pakfire_httpclient_total_downloadsize(client
);
322 // If we know the downloadsize, we can show more details
325 PAKFIRE_PROGRESS_SHOW_BYTES_TRANSFERRED
|
326 PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED
;
329 PAKFIRE_PROGRESS_SHOW_COUNTER
;
332 // Fetch the total number of queued transfers
333 const unsigned int num_queued_xfers
= pakfire_httpclient_total_queued_xfers(client
);
335 // Create a new progress indicator
336 r
= pakfire_progress_create(&progress
, client
->ctx
, progress_flags
, NULL
);
342 r
= pakfire_progress_set_title(progress
, title
);
347 // Start the progress
348 r
= pakfire_progress_start(progress
, (downloadsize
) ? downloadsize
: num_queued_xfers
);
353 // Make sure that we have up to parallel transfers active
355 r
= pakfire_httpclient_start_transfers(client
, progress
, &running_xfers
);
361 r
= curl_multi_perform(client
->curl
, &still_running
);
363 CTX_ERROR(client
->ctx
, "cURL error: %s\n", curl_easy_strerror(r
));
368 // Read the next message
369 msg
= curl_multi_info_read(client
->curl
, &msgs_left
);
375 // Update reference to transfer
376 curl_easy_getinfo(msg
->easy_handle
, CURLINFO_PRIVATE
, &xfer
);
379 curl_multi_remove_handle(client
->curl
, pakfire_xfer_handle(xfer
));
381 // Find the matching xfer element
382 x
= pakfire_httpclient_xfer_find_running(client
, xfer
);
384 // Remove the transfer from the running list
386 TAILQ_REMOVE(&client
->xfers_running
, x
, nodes
);
389 // Call the done callback
390 r
= pakfire_xfer_done(xfer
, msg
->data
.result
);
392 // If we are asked to try again we will re-queue the transfer
395 TAILQ_INSERT_TAIL(&client
->xfers_queued
, x
, nodes
);
398 // Otherwise this transfer has finished
401 pakfire_httpclient_xfer_free(x
);
412 CTX_ERROR(client
->ctx
, "Received unhandled cURL message %u\n", msg
->msg
);
417 // Wait a little before going through the loop again
419 curl_multi_wait(client
->curl
, NULL
, 0, 100, NULL
);
420 } while (still_running
|| !TAILQ_EMPTY(&client
->xfers_queued
));
423 r
= pakfire_progress_finish(progress
);
428 // If the client was aborted, we need to fail all remaining running transfers
429 while (!TAILQ_EMPTY(&client
->xfers_running
)) {
430 x
= TAILQ_LAST(&client
->xfers_running
, xfers_running
);
431 TAILQ_REMOVE(&client
->xfers_running
, x
, nodes
);
434 pakfire_xfer_fail(x
->xfer
);
438 pakfire_progress_unref(progress
);