Files
crosspoint-reader-mod/src/network/HttpDownloader.cpp
jpirnay cb24947477 feat: Add central logging pragma (#843)
## Summary

* Definition and use of a central LOG function, that can later be
extended or completely be removed (for public use where debugging
information may not be required) to save flash by suppressing the
-DENABLE_SERIAL_LOG like in the slim branch

* **What changes are included?**

## Additional Context
* By using the central logger the usual:
```
#include <HardwareSerial.h>
...
  Serial.printf("[%lu] [WCS] Obfuscating/deobfuscating %zu bytes\n", millis(), data.size());
```
would then become
```
#include <Logging.h>
...
  LOG_DBG("WCS", "Obfuscating/deobfuscating %zu bytes", data.size());
```
You do have ``LOG_DBG`` for debug messages, ``LOG_ERR`` for error
messages and ``LOG_INF`` for informational messages. Depending on the
verbosity level defined (see below) soe of these message types will be
suppressed/not-compiled.

* The normal compilation (default) will create a firmware.elf file of
42.194.356 bytes, the same code via slim will create 42.024.048 bytes -
170.308 bytes less
* Firmware.bin : 6.469.984 bytes for default, 6.418.672 bytes for slim -
51.312 bytes less


### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _NO_

---------

Co-authored-by: Xuan Son Nguyen <son@huggingface.co>
2026-02-13 12:16:39 +01:00

174 lines
4.9 KiB
C++

#include "HttpDownloader.h"
#include <HTTPClient.h>
#include <Logging.h>
#include <StreamString.h>
#include <WiFiClient.h>
#include <WiFiClientSecure.h>
#include <base64.h>
#include <cstring>
#include <memory>
#include "CrossPointSettings.h"
#include "util/UrlUtils.h"
bool HttpDownloader::fetchUrl(const std::string& url, Stream& outContent) {
// Use WiFiClientSecure for HTTPS, regular WiFiClient for HTTP
std::unique_ptr<WiFiClient> client;
if (UrlUtils::isHttpsUrl(url)) {
auto* secureClient = new WiFiClientSecure();
secureClient->setInsecure();
client.reset(secureClient);
} else {
client.reset(new WiFiClient());
}
HTTPClient http;
LOG_DBG("HTTP", "Fetching: %s", url.c_str());
http.begin(*client, url.c_str());
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.addHeader("User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION);
// Add Basic HTTP auth if credentials are configured
if (strlen(SETTINGS.opdsUsername) > 0 && strlen(SETTINGS.opdsPassword) > 0) {
std::string credentials = std::string(SETTINGS.opdsUsername) + ":" + SETTINGS.opdsPassword;
String encoded = base64::encode(credentials.c_str());
http.addHeader("Authorization", "Basic " + encoded);
}
const int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
LOG_ERR("HTTP", "Fetch failed: %d", httpCode);
http.end();
return false;
}
http.writeToStream(&outContent);
http.end();
LOG_DBG("HTTP", "Fetch success");
return true;
}
bool HttpDownloader::fetchUrl(const std::string& url, std::string& outContent) {
StreamString stream;
if (!fetchUrl(url, stream)) {
return false;
}
outContent = stream.c_str();
return true;
}
HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string& url, const std::string& destPath,
ProgressCallback progress) {
// Use WiFiClientSecure for HTTPS, regular WiFiClient for HTTP
std::unique_ptr<WiFiClient> client;
if (UrlUtils::isHttpsUrl(url)) {
auto* secureClient = new WiFiClientSecure();
secureClient->setInsecure();
client.reset(secureClient);
} else {
client.reset(new WiFiClient());
}
HTTPClient http;
LOG_DBG("HTTP", "Downloading: %s", url.c_str());
LOG_DBG("HTTP", "Destination: %s", destPath.c_str());
http.begin(*client, url.c_str());
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
http.addHeader("User-Agent", "CrossPoint-ESP32-" CROSSPOINT_VERSION);
// Add Basic HTTP auth if credentials are configured
if (strlen(SETTINGS.opdsUsername) > 0 && strlen(SETTINGS.opdsPassword) > 0) {
std::string credentials = std::string(SETTINGS.opdsUsername) + ":" + SETTINGS.opdsPassword;
String encoded = base64::encode(credentials.c_str());
http.addHeader("Authorization", "Basic " + encoded);
}
const int httpCode = http.GET();
if (httpCode != HTTP_CODE_OK) {
LOG_ERR("HTTP", "Download failed: %d", httpCode);
http.end();
return HTTP_ERROR;
}
const size_t contentLength = http.getSize();
LOG_DBG("HTTP", "Content-Length: %zu", contentLength);
// Remove existing file if present
if (Storage.exists(destPath.c_str())) {
Storage.remove(destPath.c_str());
}
// Open file for writing
FsFile file;
if (!Storage.openFileForWrite("HTTP", destPath.c_str(), file)) {
LOG_ERR("HTTP", "Failed to open file for writing");
http.end();
return FILE_ERROR;
}
// Get the stream for chunked reading
WiFiClient* stream = http.getStreamPtr();
if (!stream) {
LOG_ERR("HTTP", "Failed to get stream");
file.close();
Storage.remove(destPath.c_str());
http.end();
return HTTP_ERROR;
}
// Download in chunks
uint8_t buffer[DOWNLOAD_CHUNK_SIZE];
size_t downloaded = 0;
const size_t total = contentLength > 0 ? contentLength : 0;
while (http.connected() && (contentLength == 0 || downloaded < contentLength)) {
const size_t available = stream->available();
if (available == 0) {
delay(1);
continue;
}
const size_t toRead = available < DOWNLOAD_CHUNK_SIZE ? available : DOWNLOAD_CHUNK_SIZE;
const size_t bytesRead = stream->readBytes(buffer, toRead);
if (bytesRead == 0) {
break;
}
const size_t written = file.write(buffer, bytesRead);
if (written != bytesRead) {
LOG_ERR("HTTP", "Write failed: wrote %zu of %zu bytes", written, bytesRead);
file.close();
Storage.remove(destPath.c_str());
http.end();
return FILE_ERROR;
}
downloaded += bytesRead;
if (progress && total > 0) {
progress(downloaded, total);
}
}
file.close();
http.end();
LOG_DBG("HTTP", "Downloaded %zu bytes", downloaded);
// Verify download size if known
if (contentLength > 0 && downloaded != contentLength) {
LOG_ERR("HTTP", "Size mismatch: got %zu, expected %zu", downloaded, contentLength);
Storage.remove(destPath.c_str());
return HTTP_ERROR;
}
return OK;
}