## Summary - Upgrade platform from espressif32 6.12.0 (Arduino Core 2.0.17) to pioarduino 55.03.37 (Arduino Core 3.3.7, ESP-IDF 5.5.2) - Add WebDAV Class 1 server (RFC 4918) - SD card can be mounted as a network drive - I also slightly fixed the SDK and also made a [pull request ](https://github.com/open-x4-epaper/community-sdk/pull/21) First PR #1030 (was closed because the implementation was based on an old version of the libraries) Issue #439 --------- Co-authored-by: Dave Allie <dave@daveallie.com>
174 lines
4.9 KiB
C++
174 lines
4.9 KiB
C++
#include "HttpDownloader.h"
|
|
|
|
#include <HTTPClient.h>
|
|
#include <Logging.h>
|
|
#include <NetworkClient.h>
|
|
#include <NetworkClientSecure.h>
|
|
#include <StreamString.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 NetworkClientSecure for HTTPS, regular NetworkClient for HTTP
|
|
std::unique_ptr<NetworkClient> client;
|
|
if (UrlUtils::isHttpsUrl(url)) {
|
|
auto* secureClient = new NetworkClientSecure();
|
|
secureClient->setInsecure();
|
|
client.reset(secureClient);
|
|
} else {
|
|
client.reset(new NetworkClient());
|
|
}
|
|
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 NetworkClientSecure for HTTPS, regular NetworkClient for HTTP
|
|
std::unique_ptr<NetworkClient> client;
|
|
if (UrlUtils::isHttpsUrl(url)) {
|
|
auto* secureClient = new NetworkClientSecure();
|
|
secureClient->setInsecure();
|
|
client.reset(secureClient);
|
|
} else {
|
|
client.reset(new NetworkClient());
|
|
}
|
|
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
|
|
NetworkClient* 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;
|
|
}
|