Download Files in C++ Using LibCurl and Indicators Progress Bars

Introduction

Downloading files and showing the download progress in Python is simple. However, sometimes we would like to do the same thing in a C++ program.

In this blog post, I would like to show how to download files using the LibCurl library and the indicators progress bar library in C++.

Download C++ Application

Download Dependencies

We have to install the following dependencies.

1
2
$ sudo apt-get update
$ sudo apt-get install build-essential wget libcurl4-openssl-dev ca-certificates cmake

Alternatively, we could also use Docker container created from the following Dockerfile.

1
2
3
4
5
6
7
8
9
10
11
12
13
FROM ubuntu:20.04

ENV DEBIAN_FRONTEND noninteractive

# Install package dependencies
RUN apt-get update
RUN apt-get install -y --no-install-recommends \
build-essential \
wget \
libcurl4-openssl-dev \
ca-certificates \
cmake
RUN apt-get clean

To display a progress bar for downloading the file, we could use the indicators header-only library. Just download the header file and compile the download application with the header file.

1
$ wget https://raw.githubusercontent.com/p-ranav/indicators/v2.2/single_include/indicators/indicators.hpp

Implement Application

The simple download application was implemented as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#include <curl/curl.h>
#include <curl/easy.h>

#include <cstdio>
#include <iostream>
#include <stdexcept>
#include <string>

#include "indicators.hpp"

// https://curl.se/libcurl/c/CURLOPT_XFERINFOFUNCTION.html
int download_progress_callback(void* clientp, curl_off_t dltotal,
curl_off_t dlnow, curl_off_t ultotal,
curl_off_t ulnow)
{
indicators::ProgressBar* progress_bar =
static_cast<indicators::ProgressBar*>(clientp);

if (progress_bar->is_completed())
{
;
}
else if (dltotal == 0)
{
progress_bar->set_progress(0);
}
else
{
int percentage =
static_cast<float>(dlnow) / static_cast<float>(dltotal) * 100;
progress_bar->set_progress(percentage);
}

// If your callback function returns CURL_PROGRESSFUNC_CONTINUE it will
// cause libcurl to continue executing the default progress function. return
// CURL_PROGRESSFUNC_CONTINUE;

return 0;
}

int download_progress_default_callback(void* clientp, curl_off_t dltotal,
curl_off_t dlnow, curl_off_t ultotal,
curl_off_t ulnow)
{
return CURL_PROGRESSFUNC_CONTINUE;
}

std::string extract_file_name(const std::string& url)
{
int i = url.size();
for (; i >= 0; i--)
{
if (url[i] == '/')
{
break;
}
}

return url.substr(i + 1, url.size() - 1);
}

bool download(const std::string& url, const std::string& file_path)
{
const std::string file_name = extract_file_name(url);

// Hide cursor
indicators::show_console_cursor(false);

indicators::ProgressBar progress_bar{
indicators::option::BarWidth{30}, indicators::option::Start{" ["},
indicators::option::Fill{"█"}, indicators::option::Lead{"█"},
indicators::option::Remainder{"-"}, indicators::option::End{"]"},
indicators::option::PrefixText{file_name},
// indicators::option::ForegroundColor{indicators::Color::yellow},
indicators::option::ShowElapsedTime{true},
indicators::option::ShowRemainingTime{true},
// indicators::option::FontStyles{
// std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}
};

CURL* curl;
FILE* fp;
CURLcode res;
curl = curl_easy_init();
if (curl)
{
fp = fopen(file_path.c_str(), "wb");
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION,
download_progress_callback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA,
static_cast<void*>(&progress_bar));
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
// Perform a file transfer synchronously.
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
fclose(fp);
}

// Show cursor
indicators::show_console_cursor(true);

if (res == CURLE_OK)
{
return true;
}
else
{
return false;
}
}

int main(int argc, char* argv[])
{
std::string url;
std::string file_path;
if (argc == 2)
{
url = std::string{argv[1]};
file_path = extract_file_name(url);
}
else if (argc == 3)
{
url = std::string{argv[1]};
file_path = std::string{argv[2]};
}
else
{
throw std::invalid_argument{
"Insufficient arguments. Please provide URL or "
"URL and destination file path."};
}

download(url, file_path);
}

Build Application

1
$ g++ curl_download.cpp indicators.hpp -o curl_download -lcurl

Run Application

We could use the download application to download a common machine learning dataset.

1
2
$ ./curl_download https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz
cifar-10-binary.tar.gz [██████████████████████████████] [00m:23s<00m:00s]

References

Download Files in C++ Using LibCurl and Indicators Progress Bars

https://leimao.github.io/blog/Download-Using-LibCurl/

Author

Lei Mao

Posted on

07-06-2021

Updated on

07-06-2021

Licensed under


Comments