]> git.ipfire.org Git - pakfire.git/blame - src/libpakfire/httpclient.c
httpclient: Don't store finished transfers internally
[pakfire.git] / src / libpakfire / httpclient.c
CommitLineData
ba26680e
MT
1/*#############################################################################
2# #
3# Pakfire - The IPFire package management system #
4# Copyright (C) 2021 Pakfire development team #
5# #
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. #
10# #
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. #
15# #
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/>. #
18# #
19#############################################################################*/
20
21#include <errno.h>
22#include <stdlib.h>
91438bdb 23#include <sys/queue.h>
62b60e37
MT
24
25#include <curl/curl.h>
ba26680e 26
10db1fae 27#include <pakfire/httpclient.h>
62b60e37 28#include <pakfire/logging.h>
ffbc0988 29#include <pakfire/progress.h>
c26aca37 30#include <pakfire/xfer.h>
62b60e37
MT
31
32// The number of concurrent downloads
33#define MAX_PARALLEL 4
34
c26aca37
MT
35struct pakfire_xfer_element {
36 TAILQ_ENTRY(pakfire_xfer_element) nodes;
2d2ae99c 37
c26aca37 38 struct pakfire_xfer* xfer;
91438bdb
MT
39};
40
10db1fae 41struct pakfire_httpclient {
88b4d102 42 struct pakfire_ctx* ctx;
ba26680e 43 int nrefs;
62b60e37 44
91438bdb
MT
45 unsigned int parallel;
46
fcececd1
MT
47 // cURL share handle
48 CURLSH* share;
49
62b60e37
MT
50 // cURL multi handle
51 CURLM* curl;
62b60e37 52
f31b9f87 53 // Transfers
c26aca37
MT
54 TAILQ_HEAD(xfers_queued, pakfire_xfer_element) xfers_queued;
55 TAILQ_HEAD(xfers_running, pakfire_xfer_element) xfers_running;
f31b9f87 56};
18824be9 57
10db1fae 58static int pakfire_httpclient_setup_curl(struct pakfire_httpclient* client) {
0421c1df
MT
59 int r;
60
61 // Initialize cURL
62 r = curl_global_init(CURL_GLOBAL_DEFAULT);
63 if (r) {
10db1fae 64 CTX_ERROR(client->ctx, "Could not initialize cURL: %d\n", r);
0421c1df
MT
65 return r;
66 }
67
fcececd1 68 // Create a new share handle
10db1fae
MT
69 client->share = curl_share_init();
70 if (!client->share) {
71 CTX_ERROR(client->ctx, "Could not setup cURL share handle\n");
fcececd1
MT
72 return 1;
73 }
74
62b60e37 75 // Create a new multi handle
10db1fae
MT
76 client->curl = curl_multi_init();
77 if (!client->curl) {
78 CTX_ERROR(client->ctx, "Could not create cURL multi handle\n");
62b60e37
MT
79 return 1;
80 }
81
fcececd1 82 // Share connections between handles
10db1fae 83 r = curl_share_setopt(client->share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
fcececd1 84 if (r) {
10db1fae 85 CTX_ERROR(client->ctx, "Could not configure the share handle: %s\n",
fcececd1
MT
86 curl_share_strerror(r));
87 return r;
88 }
89
62b60e37
MT
90 return 0;
91}
92
10db1fae 93static struct pakfire_xfer_element* pakfire_httpclient_xfer_create(struct pakfire_xfer* xfer) {
c26aca37
MT
94 struct pakfire_xfer_element* x = NULL;
95
96 // Allocate a new element
97 x = calloc(1, sizeof(*x));
98 if (!x)
99 return NULL;
100
101 // Store a reference to the xfer
102 x->xfer = pakfire_xfer_ref(xfer);
103
104 return x;
105}
106
10db1fae
MT
107static struct pakfire_xfer_element* pakfire_httpclient_xfer_find_running(
108 struct pakfire_httpclient* client, struct pakfire_xfer* xfer) {
c26aca37
MT
109 struct pakfire_xfer_element* x = NULL;
110
10db1fae 111 TAILQ_FOREACH(x, &client->xfers_running, nodes) {
c26aca37
MT
112 if (x->xfer == xfer)
113 return x;
114 }
115
116 return NULL;
117}
118
10db1fae 119static void pakfire_httpclient_xfer_free(struct pakfire_xfer_element* x) {
c26aca37
MT
120 if (x->xfer)
121 pakfire_xfer_unref(x->xfer);
122
123 free(x);
91438bdb
MT
124}
125
10db1fae 126static void pakfire_httpclient_free(struct pakfire_httpclient* client) {
c26aca37 127 struct pakfire_xfer_element* x = NULL;
f31b9f87
MT
128
129 // Free any queued transfers
10db1fae
MT
130 while (!TAILQ_EMPTY(&client->xfers_queued)) {
131 x = TAILQ_LAST(&client->xfers_queued, xfers_queued);
132 TAILQ_REMOVE(&client->xfers_queued, x, nodes);
f31b9f87 133
10db1fae 134 pakfire_httpclient_xfer_free(x);
0691b7c9 135 }
91438bdb 136
f31b9f87 137 // Free any running transfers
10db1fae
MT
138 while (!TAILQ_EMPTY(&client->xfers_running)) {
139 x = TAILQ_LAST(&client->xfers_running, xfers_running);
140 TAILQ_REMOVE(&client->xfers_running, x, nodes);
f31b9f87 141
10db1fae 142 pakfire_httpclient_xfer_free(x);
f31b9f87
MT
143 }
144
10db1fae
MT
145 if (client->share)
146 curl_share_cleanup(client->share);
147 if (client->curl)
148 curl_multi_cleanup(client->curl);
149 if (client->ctx)
150 pakfire_ctx_unref(client->ctx);
151 free(client);
ba26680e
MT
152}
153
10db1fae
MT
154int pakfire_httpclient_create(struct pakfire_httpclient** client, struct pakfire_ctx* ctx) {
155 struct pakfire_httpclient* c = NULL;
0421c1df
MT
156 int r;
157
d11380dd
MT
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");
161 return -EPERM;
464c81b3
MT
162 }
163
f31b9f87 164 // Allocate a new object
10db1fae
MT
165 c = calloc(1, sizeof(*c));
166 if (!c)
f31b9f87 167 return -ENOMEM;
ba26680e 168
88b4d102 169 // Store reference to the context
10db1fae 170 c->ctx = pakfire_ctx_ref(ctx);
ba26680e
MT
171
172 // Initialize reference counting
10db1fae 173 c->nrefs = 1;
ba26680e 174
91438bdb 175 // Set parallelism
10db1fae 176 c->parallel = MAX_PARALLEL;
91438bdb 177
f31b9f87 178 // Init transfer queues
10db1fae
MT
179 TAILQ_INIT(&c->xfers_queued);
180 TAILQ_INIT(&c->xfers_running);
91438bdb 181
62b60e37 182 // Setup cURL
10db1fae 183 r = pakfire_httpclient_setup_curl(c);
62b60e37
MT
184 if (r)
185 goto ERROR;
186
f31b9f87 187 // Success
10db1fae 188 *client = c;
ba26680e
MT
189
190 return 0;
62b60e37
MT
191
192ERROR:
10db1fae 193 pakfire_httpclient_free(c);
62b60e37
MT
194
195 return r;
ba26680e
MT
196}
197
10db1fae
MT
198struct pakfire_httpclient* pakfire_httpclient_ref(struct pakfire_httpclient* client) {
199 ++client->nrefs;
ba26680e 200
10db1fae 201 return client;
ba26680e
MT
202}
203
10db1fae
MT
204struct pakfire_httpclient* pakfire_httpclient_unref(struct pakfire_httpclient* client) {
205 if (--client->nrefs > 0)
206 return client;
ba26680e 207
10db1fae 208 pakfire_httpclient_free(client);
ba26680e
MT
209 return NULL;
210}
211
10db1fae
MT
212CURLSH* pakfire_httpclient_share(struct pakfire_httpclient* client) {
213 return client->share;
fcb74f50
MT
214}
215
10db1fae
MT
216int 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);
f31b9f87
MT
219}
220
10db1fae 221int pakfire_httpclient_enqueue_xfer(struct pakfire_httpclient* client,
c26aca37
MT
222 struct pakfire_xfer* xfer) {
223 struct pakfire_xfer_element* x = NULL;
f31b9f87 224
c26aca37 225 // Create a new queueable object
10db1fae 226 x = pakfire_httpclient_xfer_create(xfer);
c26aca37 227 if (!x)
f31b9f87
MT
228 return -errno;
229
f31b9f87 230 // Push this transfer onto the queue
10db1fae 231 TAILQ_INSERT_HEAD(&client->xfers_queued, x, nodes);
f31b9f87
MT
232
233 return 0;
234}
235
10db1fae 236static size_t pakfire_httpclient_total_downloadsize(struct pakfire_httpclient* client) {
c26aca37 237 struct pakfire_xfer_element* x = NULL;
f31b9f87
MT
238 size_t size;
239
240 size_t total_size = 0;
241
10db1fae 242 TAILQ_FOREACH(x, &client->xfers_queued, nodes) {
c26aca37 243 size = pakfire_xfer_get_size(x->xfer);
f31b9f87
MT
244
245 // Return zero if the size isn't known
246 if (!size)
247 return 0;
248
249 total_size += size;
250 }
251
252 return total_size;
253}
254
10db1fae 255static unsigned int pakfire_httpclient_total_queued_xfers(struct pakfire_httpclient* client) {
c26aca37 256 struct pakfire_xfer_element* x = NULL;
f31b9f87
MT
257 unsigned int counter = 0;
258
10db1fae 259 TAILQ_FOREACH(x, &client->xfers_queued, nodes)
f31b9f87
MT
260 counter++;
261
262 return counter;
263}
264
10db1fae 265static int pakfire_httpclient_start_transfers(struct pakfire_httpclient* client,
f31b9f87 266 struct pakfire_progress* progress, unsigned int* running_transfers) {
c26aca37 267 struct pakfire_xfer_element* x = NULL;
91438bdb 268 int r;
f31b9f87
MT
269
270 // Keep running until we have reached our ceiling
10db1fae 271 while (*running_transfers < client->parallel) {
f31b9f87 272 // We are done if there are no more transfers in the queue
10db1fae 273 if (TAILQ_EMPTY(&client->xfers_queued))
f31b9f87
MT
274 break;
275
276 // Fetch the next transfer
10db1fae
MT
277 x = TAILQ_LAST(&client->xfers_queued, xfers_queued);
278 TAILQ_REMOVE(&client->xfers_queued, x, nodes);
c26aca37
MT
279
280 // Prepare the xfer
281 r = pakfire_xfer_prepare(x->xfer, progress, 0);
282 if (r)
283 goto ERROR;
f31b9f87 284
c26aca37 285 // Add the handle to cURL
10db1fae 286 r = curl_multi_add_handle(client->curl, pakfire_xfer_handle(x->xfer));
f31b9f87 287 if (r) {
10db1fae 288 CTX_ERROR(client->ctx, "Adding handle failed: %s\n", curl_multi_strerror(r));
c26aca37 289 goto ERROR;
f31b9f87
MT
290 }
291
10db1fae 292 TAILQ_INSERT_TAIL(&client->xfers_running, x, nodes);
f31b9f87
MT
293 (*running_transfers)++;
294 }
295
296 return 0;
c26aca37
MT
297
298ERROR:
469f2dd8 299 pakfire_httpclient_xfer_free(x);
c26aca37
MT
300
301 return r;
f31b9f87
MT
302}
303
10db1fae 304int pakfire_httpclient_run(struct pakfire_httpclient* client, const char* title) {
f31b9f87 305 struct pakfire_progress* progress = NULL;
c26aca37
MT
306 struct pakfire_xfer* xfer = NULL;
307 unsigned int running_xfers = 0;
f31b9f87
MT
308 int progress_flags =
309 PAKFIRE_PROGRESS_SHOW_PERCENTAGE |
310 PAKFIRE_PROGRESS_SHOW_ETA;
311 int r = 0;
312
c26aca37
MT
313 struct pakfire_xfer_element* x = NULL;
314
f31b9f87 315 CURLMsg* msg = NULL;
62b60e37
MT
316 int still_running;
317 int msgs_left = -1;
f31b9f87
MT
318
319 // Fetch the total downloadsize
10db1fae 320 const size_t downloadsize = pakfire_httpclient_total_downloadsize(client);
f31b9f87
MT
321
322 // If we know the downloadsize, we can show more details
323 if (downloadsize) {
324 progress_flags |=
325 PAKFIRE_PROGRESS_SHOW_BYTES_TRANSFERRED |
326 PAKFIRE_PROGRESS_SHOW_TRANSFER_SPEED;
327 } else {
328 progress_flags |=
329 PAKFIRE_PROGRESS_SHOW_COUNTER;
330 }
331
332 // Fetch the total number of queued transfers
10db1fae 333 const unsigned int num_queued_xfers = pakfire_httpclient_total_queued_xfers(client);
f31b9f87
MT
334
335 // Create a new progress indicator
10db1fae 336 r = pakfire_progress_create(&progress, client->ctx, progress_flags, NULL);
f31b9f87
MT
337 if (r)
338 goto ERROR;
339
340 // Set the title
341 if (title) {
342 r = pakfire_progress_set_title(progress, title);
343 if (r)
344 goto ERROR;
345 }
346
347 // Start the progress
c26aca37 348 r = pakfire_progress_start(progress, (downloadsize) ? downloadsize : num_queued_xfers);
f31b9f87
MT
349 if (r)
350 goto ERROR;
62b60e37
MT
351
352 do {
91438bdb 353 // Make sure that we have up to parallel transfers active
f31b9f87 354 if (!r) {
10db1fae 355 r = pakfire_httpclient_start_transfers(client, progress, &running_xfers);
f31b9f87
MT
356 if (r)
357 goto ERROR;
358 }
91438bdb 359
f31b9f87 360 // Run cURL
10db1fae 361 r = curl_multi_perform(client->curl, &still_running);
f31b9f87 362 if (r) {
10db1fae 363 CTX_ERROR(client->ctx, "cURL error: %s\n", curl_easy_strerror(r));
f31b9f87 364 goto ERROR;
91438bdb
MT
365 }
366
f31b9f87
MT
367 for (;;) {
368 // Read the next message
10db1fae 369 msg = curl_multi_info_read(client->curl, &msgs_left);
f31b9f87
MT
370 if (!msg)
371 break;
372
373 switch (msg->msg) {
374 case CURLMSG_DONE:
375 // Update reference to transfer
c26aca37 376 curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &xfer);
f31b9f87
MT
377
378 // Remove the handle
10db1fae 379 curl_multi_remove_handle(client->curl, pakfire_xfer_handle(xfer));
c26aca37
MT
380
381 // Find the matching xfer element
10db1fae 382 x = pakfire_httpclient_xfer_find_running(client, xfer);
f31b9f87
MT
383
384 // Remove the transfer from the running list
c26aca37 385 if (x)
10db1fae 386 TAILQ_REMOVE(&client->xfers_running, x, nodes);
c26aca37 387 running_xfers--;
f31b9f87
MT
388
389 // Call the done callback
c26aca37 390 r = pakfire_xfer_done(xfer, msg->data.result);
f31b9f87
MT
391 switch (-r) {
392 // If we are asked to try again we will re-queue the transfer
393 case EAGAIN:
c26aca37 394 if (x)
10db1fae 395 TAILQ_INSERT_TAIL(&client->xfers_queued, x, nodes);
f31b9f87
MT
396 break;
397
398 // Otherwise this transfer has finished
399 default:
c26aca37 400 if (x)
469f2dd8 401 pakfire_httpclient_xfer_free(x);
f31b9f87
MT
402 if (r)
403 goto ERROR;
404 break;
7da3f0b0
MT
405 }
406
f31b9f87 407 // Reset transfer
c26aca37 408 xfer = NULL;
f31b9f87 409 break;
62b60e37 410
f31b9f87 411 default:
10db1fae 412 CTX_ERROR(client->ctx, "Received unhandled cURL message %u\n", msg->msg);
f31b9f87 413 break;
62b60e37
MT
414 }
415 }
416
417 // Wait a little before going through the loop again
418 if (still_running)
10db1fae
MT
419 curl_multi_wait(client->curl, NULL, 0, 100, NULL);
420 } while (still_running || !TAILQ_EMPTY(&client->xfers_queued));
62b60e37 421
f31b9f87
MT
422 // We are finished!
423 r = pakfire_progress_finish(progress);
424 if (r)
425 goto ERROR;
426
427ERROR:
10db1fae
MT
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);
f31b9f87
MT
432
433 // Fail the transfer
6aed39be 434 pakfire_xfer_fail(x->xfer);
f31b9f87
MT
435 }
436
437 if (progress)
438 pakfire_progress_unref(progress);
439
440 return r;
ba26680e 441}