Skip to content

Commit 71eaa8e

Browse files
committed
Add file download utility
1 parent 898e5b9 commit 71eaa8e

2 files changed

Lines changed: 164 additions & 0 deletions

File tree

main/http_download.c

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
#include "esp_event.h"
2+
#include "esp_http_client.h"
3+
#include "esp_system.h"
4+
#include "esp_vfs.h"
5+
#include "esp_vfs_fat.h"
6+
#include "freertos/FreeRTOS.h"
7+
#include "freertos/queue.h"
8+
#include "freertos/task.h"
9+
10+
static const char* TAG = "HTTP download";
11+
12+
typedef struct {
13+
FILE* fd; // For downloading directly to file on filesystem
14+
uint8_t** buffer; // Dynamically allocated buffer for downloading to RAM (malloced in event handler, used if fd is
15+
// not set)
16+
size_t size; // File size as indicated by content-length header (set in event handler)
17+
size_t received; // Amount of data received (set in event handler)
18+
bool error; // Indication that an error event happened (set in event handler)
19+
bool connected; // Indication that the HTTP client has connected to the server (set in event handler)
20+
bool finished; // Indication that the operation has completed (set in event handler)
21+
bool disconnected; // Indication that the HTTP client has disconnected from the server (set in event handler)
22+
bool out_of_memory; // Indication that malloc failed
23+
bool out_of_allocated; // Indication that the server sent more data than indicated with the content-length header
24+
} http_download_info_t;
25+
26+
static esp_err_t _event_handler(esp_http_client_event_t* evt) {
27+
http_download_info_t* info = (http_download_info_t*)evt->user_data;
28+
switch (evt->event_id) {
29+
case HTTP_EVENT_ERROR:
30+
info->error = true;
31+
break;
32+
case HTTP_EVENT_ON_CONNECTED:
33+
info->connected = true;
34+
break;
35+
case HTTP_EVENT_HEADERS_SENT:
36+
break;
37+
case HTTP_EVENT_ON_HEADER: {
38+
const char content_length_key[] = "Content-Length";
39+
if ((strlen(evt->header_key) == strlen(content_length_key)) &&
40+
(strncasecmp(content_length_key, evt->header_key, strlen(content_length_key)) == 0)) {
41+
// Header value is content length
42+
info->size = atoi(evt->header_value);
43+
ESP_LOGI(TAG, "Size is known: %u bytes", info->size);
44+
if ((info->size > 0) &&
45+
(info->buffer != NULL)) { // Buffer poiner is set, buffer pointer points to NULL
46+
*info->buffer = malloc(info->size);
47+
ESP_LOGI(TAG, "Allocated buffer (%u bytes)", info->size);
48+
if (*info->buffer == NULL) {
49+
info->out_of_memory = true;
50+
return ESP_ERR_NO_MEM;
51+
}
52+
}
53+
} else {
54+
ESP_LOGD(TAG, "Header: key=%s, value=%s", evt->header_key, evt->header_value);
55+
}
56+
break;
57+
}
58+
case HTTP_EVENT_ON_DATA:
59+
if (info->fd != NULL) { // Write directly to file on filesystem
60+
ESP_LOGI(TAG, "Writing to file @ %p (%u bytes): %u of %u bytes.", info->fd, evt->data_len,
61+
info->received + evt->data_len, info->size);
62+
fwrite(evt->data, 1, evt->data_len, info->fd);
63+
} else if (info->buffer != NULL && *info->buffer != NULL) {
64+
if (info->received + evt->data_len <= info->size) {
65+
uint8_t* dest = &((*info->buffer)[info->received]);
66+
ESP_LOGI(TAG, "Writing to RAM @ %p (%u bytes): %u of %u bytes.", dest, evt->data_len,
67+
info->received + evt->data_len, info->size);
68+
memcpy(dest, evt->data, evt->data_len);
69+
} else {
70+
ESP_LOGI(TAG, "Downloaded too much? %u with %u in content-length header",
71+
info->received + evt->data_len, info->size);
72+
info->out_of_allocated = true;
73+
return ESP_ERR_NO_MEM;
74+
}
75+
} else {
76+
return ESP_FAIL;
77+
}
78+
info->received += evt->data_len;
79+
break;
80+
case HTTP_EVENT_ON_FINISH:
81+
info->finished = true;
82+
break;
83+
case HTTP_EVENT_DISCONNECTED:
84+
info->disconnected = true;
85+
break;
86+
case HTTP_EVENT_REDIRECT:
87+
break;
88+
}
89+
return ESP_OK;
90+
}
91+
92+
static bool download_success(esp_err_t err, http_download_info_t* info) {
93+
return (err == ESP_OK) && (!(info->error || info->out_of_allocated || info->out_of_memory)) && info->finished &&
94+
(info->received == info->size);
95+
}
96+
97+
static bool _download_file(const char* url, const char* path) {
98+
FILE* fd = fopen(path, "w");
99+
if (fd == NULL) {
100+
ESP_LOGE(TAG, "Failed to open file");
101+
return false;
102+
}
103+
104+
http_download_info_t info = {0};
105+
info.fd = fd;
106+
107+
esp_http_client_config_t config = {.url = url,
108+
.use_global_ca_store = true,
109+
.keep_alive_enable = true,
110+
.timeout_ms = 10000,
111+
.user_data = (void*)&info,
112+
.event_handler = _event_handler};
113+
esp_http_client_handle_t client = esp_http_client_init(&config);
114+
esp_err_t err = esp_http_client_perform(client);
115+
fclose(fd);
116+
esp_http_client_cleanup(client);
117+
return download_success(err, &info);
118+
}
119+
120+
bool download_file(const char* url, const char* path) {
121+
int retry = 3;
122+
while (retry--) {
123+
if (_download_file(url, path)) return true;
124+
ESP_LOGI(TAG, "Download waiting to retry ...");
125+
vTaskDelay(pdMS_TO_TICKS(5000));
126+
}
127+
return false;
128+
}
129+
130+
static bool _download_ram(const char* url, uint8_t** ptr, size_t* size) {
131+
http_download_info_t info = {0};
132+
info.buffer = ptr;
133+
esp_http_client_config_t config = {.url = url,
134+
.use_global_ca_store = true,
135+
.keep_alive_enable = true,
136+
.timeout_ms = 10000,
137+
.user_data = (void*)&info,
138+
.event_handler = _event_handler};
139+
esp_http_client_handle_t client = esp_http_client_init(&config);
140+
esp_err_t err = esp_http_client_perform(client);
141+
bool success = download_success(err, &info);
142+
if (success && (size != NULL)) *size = info.size;
143+
ESP_LOGI(TAG, "Buffer: %p -> %p", ptr, *ptr);
144+
esp_http_client_cleanup(client);
145+
return success;
146+
}
147+
148+
bool download_ram(const char* url, uint8_t** ptr, size_t* size) {
149+
int retry = 3;
150+
while (retry--) {
151+
if (_download_ram(url, ptr, size)) return true;
152+
ESP_LOGI(TAG, "Download waiting to retry ...");
153+
vTaskDelay(pdMS_TO_TICKS(5000));
154+
}
155+
return false;
156+
}

main/http_download.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#pragma once
2+
3+
#include <stdbool.h>
4+
#include <stddef.h>
5+
#include <stdint.h>
6+
7+
bool download_file(const char* url, const char* path);
8+
bool download_ram(const char* url, uint8_t** ptr, size_t* size);

0 commit comments

Comments
 (0)