Skip to content

Commit 8ef1e98

Browse files
committed
add read file by blocks, rewrite handle_download for sending by blocks/chunks and process large files
1 parent 74522a4 commit 8ef1e98

File tree

6 files changed

+98
-63
lines changed

6 files changed

+98
-63
lines changed

components/sd_file_server/__init__.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from esphome.components import web_server_base
44
from esphome.components.web_server_base import CONF_WEB_SERVER_BASE_ID
55
from esphome.const import (
6-
CONF_ID
6+
CONF_ID,
7+
CONF_BUFFER_SIZE
78
)
89
from esphome.core import coroutine_with_priority, CORE
910
from .. import sd_mmc_card
@@ -33,6 +34,7 @@
3334
cv.Optional(CONF_ENABLE_DELETION, default=False): cv.boolean,
3435
cv.Optional(CONF_ENABLE_DOWNLOAD, default=False): cv.boolean,
3536
cv.Optional(CONF_ENABLE_UPLOAD, default=False): cv.boolean,
37+
cv.Optional(CONF_BUFFER_SIZE, default=65536): cv.int_range(256, 65536),
3638
}
3739
).extend(cv.COMPONENT_SCHEMA),
3840
)
@@ -50,5 +52,6 @@ async def to_code(config):
5052
cg.add(var.set_deletion_enabled(config[CONF_ENABLE_DELETION]))
5153
cg.add(var.set_download_enabled(config[CONF_ENABLE_DOWNLOAD]))
5254
cg.add(var.set_upload_enabled(config[CONF_ENABLE_UPLOAD]))
55+
cg.add(var.set_buf_size(config[CONF_BUFFER_SIZE]))
5356

5457
cg.add_define("USE_SD_CARD_WEBSERVER")

components/sd_file_server/sd_file_server.cpp

+74-48
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ void SDFileServer::dump_config() {
2323
ESP_LOGCONFIG(TAG, " Deletation Enabled: %s", TRUEFALSE(this->deletion_enabled_));
2424
ESP_LOGCONFIG(TAG, " Download Enabled : %s", TRUEFALSE(this->download_enabled_));
2525
ESP_LOGCONFIG(TAG, " Upload Enabled : %s", TRUEFALSE(this->upload_enabled_));
26+
ESP_LOGCONFIG(TAG, " Buf size : %d", this->buf_size_);
2627
}
2728

2829
bool SDFileServer::canHandle(AsyncWebServerRequest *request) {
@@ -91,7 +92,9 @@ void SDFileServer::set_download_enabled(bool allow) { this->download_enabled_ =
9192

9293
void SDFileServer::set_upload_enabled(bool allow) { this->upload_enabled_ = allow; }
9394

94-
void SDFileServer::handle_get(AsyncWebServerRequest *request) const {
95+
void SDFileServer::set_buf_size(size_t buf_size) { this->buf_size_ = buf_size; }
96+
97+
void SDFileServer::handle_get(AsyncWebServerRequest *request) {
9598
std::string extracted = this->extract_path_from_url(std::string(request->url().c_str()));
9699
std::string path = this->build_absolute_path(extracted);
97100

@@ -318,66 +321,88 @@ void SDFileServer::handle_index(AsyncWebServerRequest *request, std::string cons
318321

319322
request->send(response);
320323
}
321-
void SDFileServer::handle_download(AsyncWebServerRequest *request, std::string const &path) const {
324+
void SDFileServer::handle_download(AsyncWebServerRequest *request, std::string const &path) {
322325
if (!this->download_enabled_) {
323326
request->send(401, "application/json", "{ \"error\": \"file download is disabled\" }");
324327
return;
325328
}
326329
size_t read_len = 0;
327330

328331
#ifdef USE_ESP_IDF
329-
size_t file_size = this->sd_mmc_card_->file_size(path);
330-
if (file_size <= 0) {
331-
ESP_LOGE(TAG, "File not found: %s",path.c_str());
332-
request->send(401, "application/json", "{ \"error\": \"file not found or empty.\" }");
333-
return;
334-
}
335-
336-
RAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
337-
uint8_t *data = allocator.allocate(file_size);
338-
339-
if (data == nullptr) {
340-
ESP_LOGE(TAG, "No memory. (Size: %zu)", file_size);
341-
request->send(401, "application/json", "{ \"error\": \"no memory.\" }");
342-
return;
343-
}
344-
345-
read_len = this->sd_mmc_card_->read_file(path,data,file_size);
346-
347-
if (read_len == 0 ) {
348-
ESP_LOGE(TAG, "Failed to read file: %s",path.c_str());
349-
allocator.deallocate(data,file_size);
350-
request->send(401, "application/json", "{ \"error\": \"failed to read file\" }");
351-
return;
352-
}
353-
354-
auto *response = request->beginResponse_P(200, Path::mime_type(path).c_str(), data, read_len);
355-
356-
#else
332+
httpd_req_t *req = *request;
333+
httpd_resp_set_status(req, HTTPD_200);
334+
auto mt = new std::string(Path::mime_type(path));
335+
httpd_resp_set_type(req, mt->c_str());
336+
std::string fname = std::string("inline; filename=") + Path::file_name(path);
337+
httpd_resp_set_hdr(req, "Content-Disposition", fname.c_str());
338+
std::string content_len = std::to_string(this->sd_mmc_card_->file_size(path));
339+
httpd_resp_set_hdr(req, "content-length", content_len.c_str());
340+
httpd_resp_set_hdr(req, "access-control-allow-origin", "*");
341+
httpd_resp_set_hdr(req, "Cache-Control", "no-cache");
342+
#endif
343+
sd_mmc_card::FilePtr *fptr = this->sd_mmc_card_->open_file(path.c_str(), "r");
344+
if (fptr == nullptr)
345+
{
346+
ESP_LOGE(TAG, "Failed to open file: %s", path.c_str());
347+
request->send(401, "application/json", "{ \"error\": \"failed to open file\" }");
348+
return;
349+
}
350+
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
351+
request->onDisconnect({
352+
this->sd_mmc_card_->close_file(fptr);
353+
});
354+
auto response = request->beginResponse(Path::mime_type(path), this->sd_mmc_card_->file_size(path),
355+
[](uint8_t *buffer, size_t maxLen, size_t index) -> size_t
356+
{
357+
read_len = this->sd_mmc_card_->block_read_file(fptr, buffer, maxLen);
358+
if (read_len < 0)
359+
{
360+
ESP_LOGE(TAG, "Error sending file data");
361+
read_len = 0;
362+
}
363+
else
364+
ESP_LOGV(TAG, "Read file block %d", read_len);
365+
return read_len;
366+
});
367+
std::string fname = std::string("attachment; filename=") + Path::file_name(path);
368+
response->addHeader("access-control-allow-origin","*");
369+
response->addHeader("Content-Disposition",fname.c_str());
370+
request->send(response);
371+
#endif
357372

358-
auto file = this->sd_mmc_card_->read_file(path);
373+
#ifdef USE_ESP_IDF
374+
uint8_t *buff = this->allocator_.allocate(this->buf_size_);
359375

360-
if (file.size() == 0) {
361-
request->send(401, "application/json", "{ \"error\": \"failed to read file\" }");
376+
if (buff == nullptr) {
377+
ESP_LOGE(TAG, "No memory: %d", this->buf_size_);
378+
request->send(401, "application/json", "{ \"error\": \"no memory\" }");
362379
return;
363380
}
364-
auto *response = request->beginResponseStream(Path::mime_type(path).c_str(), file.size());
365-
response->write(file.data(), file.size());
366-
read_len = file.size();
367-
#endif
368-
369-
ESP_LOGD(TAG, "Send file: %s, size %d", path.c_str(), read_len);
370-
request->send(response);
381+
do {
382+
read_len = this->sd_mmc_card_->block_read_file(fptr, buff, this->buf_size_);
383+
ESP_LOGV(TAG, "Read file block %d", read_len);
384+
if (read_len >= 0)
385+
{
386+
esp_err_t ret = httpd_resp_send_chunk(req, (const char *)buff, read_len);
387+
if (ret != ESP_OK)
388+
{
389+
this->sd_mmc_card_->close_file(fptr);
390+
ESP_LOGE(TAG, "Error send file data: %s", esp_err_to_name(ret));
391+
this->allocator_.deallocate(buff, this->buf_size_);
392+
// request->send(500, "application/json", "{ \"error\": \"no memory\" }");
393+
return;
394+
}
395+
else
396+
ESP_LOGV(TAG, "Send file chunk %d", read_len);
397+
}
398+
} while (read_len > 0);
399+
ESP_LOGD(TAG, "Send complete");
371400

372-
#ifdef USE_ESP_IDF
373-
//TODO: esp_http_server_dispatch_event(HTTP_SERVER_EVENT_SENT_DATA, &evt_data, sizeof(esp_http_server_event_data));
374-
// return ESP_OK;
375-
allocator.deallocate(data,file_size);
401+
this->sd_mmc_card_->close_file(fptr);
402+
this->allocator_.deallocate(buff, this->buf_size_);
376403
#endif
377404
}
378405

379-
380-
381406
void SDFileServer::handle_delete(AsyncWebServerRequest *request) {
382407
if (!this->deletion_enabled_) {
383408
request->send(401, "application/json", "{ \"error\": \"file deletion is disabled\" }");
@@ -494,7 +519,8 @@ std::string Path::mime_type(std::string const &file) {
494519
{"csv", "text/csv"}, {"html", "text/html"}, {"css", "text/css"}, {"js", "text/javascript"},
495520
{"json", "application/json"}, {"xml", "application/xml"}, {"zip", "application/zip"}, {"gz", "application/gzip"},
496521
{"tar", "application/x-tar"}, {"mp4", "video/mp4"}, {"avi", "video/x-msvideo"}, {"webm", "video/webm"}};
497-
522+
523+
// auto oct_stream = std::string("application/octet-stream");
498524
std::string ext = Path::extension(file);
499525
ESP_LOGD(TAG, "ext : %s", ext.c_str());
500526
if (!ext.empty()) {
@@ -503,7 +529,7 @@ std::string Path::mime_type(std::string const &file) {
503529
if (it != file_types.end())
504530
return it->second;
505531
}
506-
return "application/octet-stream";
532+
return std::string("application/octet-stream");
507533
}
508534

509535
} // namespace sd_file_server

components/sd_file_server/sd_file_server.h

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22
#include "esphome/core/component.h"
3+
#include "esphome/core/helpers.h"
34
#include "esphome/components/web_server_base/web_server_base.h"
45
#include "../sd_mmc_card/sd_mmc_card.h"
56

@@ -23,8 +24,12 @@ class SDFileServer : public Component, public AsyncWebHandler {
2324
void set_deletion_enabled(bool);
2425
void set_download_enabled(bool);
2526
void set_upload_enabled(bool);
27+
void set_buf_size(size_t);
2628

2729
protected:
30+
#ifdef USE_ESP_IDF
31+
RAMAllocator<uint8_t> allocator_{};
32+
#endif
2833
web_server_base::WebServerBase *base_;
2934
sd_mmc_card::SdMmc *sd_mmc_card_;
3035

@@ -33,15 +38,16 @@ class SDFileServer : public Component, public AsyncWebHandler {
3338
bool deletion_enabled_;
3439
bool download_enabled_;
3540
bool upload_enabled_;
41+
size_t buf_size_;
3642

3743
std::string build_prefix() const;
3844
std::string extract_path_from_url(std::string const &) const;
3945
std::string build_absolute_path(std::string) const;
4046
void write_row(AsyncResponseStream *response, sd_mmc_card::FileInfo const &info) const;
4147
void handle_index(AsyncWebServerRequest *, std::string const &) const;
42-
void handle_get(AsyncWebServerRequest *) const;
43-
void handle_delete(AsyncWebServerRequest *);
44-
void handle_download(AsyncWebServerRequest *, std::string const &) const;
48+
void handle_get(AsyncWebServerRequest *) ;
49+
void handle_delete(AsyncWebServerRequest *) ;
50+
void handle_download(AsyncWebServerRequest *, std::string const &);
4551
};
4652

4753
struct Path {

components/sd_mmc_card/sd_mmc_card.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ struct FileSizeSensor {
3131

3232
struct FilePtr
3333
{
34-
std::string path;
34+
std::string *path;
3535
#if defined(USE_ESP_IDF)
3636
FILE *file;
3737
#elif defined(USE_ESP32_FRAMEWORK_ARDUINO)

components/sd_mmc_card/sd_mmc_card_esp32_arduino.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,9 @@ FilePtr* SdMmc::open_file(const char *path, const char* mode) {
102102
return NULL;
103103
}
104104

105-
fptr->path = build_path(path);
105+
fptr->path = new std::string(build_path(path));
106106
if ( ! SD_MMC.exists(fptr->path) ) {
107-
ESP_LOGE(TAG, "File %s does not exist", absolut_path.c_str());
107+
ESP_LOGE(TAG, "File %s does not exist", absolut_path->c_str());
108108
delete fptr->path;
109109
free(fptr);
110110
return NULL;

components/sd_mmc_card/sd_mmc_card_esp_idf.cpp

+8-8
Original file line numberDiff line numberDiff line change
@@ -140,27 +140,27 @@ FilePtr* SdMmc::open_file(const char *path, const char* mode) {
140140
ESP_LOGE(TAG, "Not enough memory");
141141
return NULL;
142142
}
143-
fptr->path = build_path(path);
143+
fptr->path = new std::string(build_path(path));
144144
fptr->file = nullptr;
145145

146-
ESP_LOGD(TAG,"Opening File full path: %s, mode %s",fptr->path.c_str(),mode);
147-
fptr->file = fopen(fptr->path.c_str(), mode);
146+
ESP_LOGD(TAG,"Opening File full path: %s, mode %s",fptr->path->c_str(),mode);
147+
fptr->file = fopen(fptr->path->c_str(), mode);
148148

149149
if (fptr->file == nullptr)
150150
{
151151
ESP_LOGE(TAG, "Cannot open file. %s",strerror(errno));
152152
delete fptr->path;
153-
free(fptr)
153+
free(fptr);
154154
return NULL;
155155
}
156156
return fptr;
157157
}
158158

159-
void SdMmc::close_file(FilePtr* fl) {
160-
if ( fl != NULL ) {
161-
fclose(fl->file);
159+
void SdMmc::close_file(FilePtr* fptr) {
160+
if ( fptr != NULL ) {
161+
fclose(fptr->file);
162162
delete fptr->path;
163-
free(fl);
163+
free(fptr);
164164
}
165165
}
166166

0 commit comments

Comments
 (0)