mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-05 07:07:38 +03:00
Our esp32 consistently dropped the last few packets of the TCP transfer in the old implementation. Only about 1/5 transfers would complete. I've refactored that entire system into an actual Calibre Device Plugin that basically uses the exact same system as the web server's file transfer protocol. I kept them separate so that we don't muddy up the existing file transfer stuff even if it's basically the same at the end of the day I didn't want to limit our ability to change it later. I've also added basic auth to OPDS and renamed that feature to OPDS Browser to just disassociate it from Calibre. --------- Co-authored-by: Arthur Tazhitdinov <lisnake@gmail.com> Co-authored-by: Dave Allie <dave@daveallie.com>
174 lines
5.2 KiB
C++
174 lines
5.2 KiB
C++
#include "HttpDownloader.h"
|
|
|
|
#include <HTTPClient.h>
|
|
#include <HardwareSerial.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;
|
|
|
|
Serial.printf("[%lu] [HTTP] Fetching: %s\n", millis(), 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) {
|
|
Serial.printf("[%lu] [HTTP] Fetch failed: %d\n", millis(), httpCode);
|
|
http.end();
|
|
return false;
|
|
}
|
|
|
|
http.writeToStream(&outContent);
|
|
|
|
http.end();
|
|
|
|
Serial.printf("[%lu] [HTTP] Fetch success\n", millis());
|
|
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;
|
|
|
|
Serial.printf("[%lu] [HTTP] Downloading: %s\n", millis(), url.c_str());
|
|
Serial.printf("[%lu] [HTTP] Destination: %s\n", millis(), 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) {
|
|
Serial.printf("[%lu] [HTTP] Download failed: %d\n", millis(), httpCode);
|
|
http.end();
|
|
return HTTP_ERROR;
|
|
}
|
|
|
|
const size_t contentLength = http.getSize();
|
|
Serial.printf("[%lu] [HTTP] Content-Length: %zu\n", millis(), contentLength);
|
|
|
|
// Remove existing file if present
|
|
if (SdMan.exists(destPath.c_str())) {
|
|
SdMan.remove(destPath.c_str());
|
|
}
|
|
|
|
// Open file for writing
|
|
FsFile file;
|
|
if (!SdMan.openFileForWrite("HTTP", destPath.c_str(), file)) {
|
|
Serial.printf("[%lu] [HTTP] Failed to open file for writing\n", millis());
|
|
http.end();
|
|
return FILE_ERROR;
|
|
}
|
|
|
|
// Get the stream for chunked reading
|
|
WiFiClient* stream = http.getStreamPtr();
|
|
if (!stream) {
|
|
Serial.printf("[%lu] [HTTP] Failed to get stream\n", millis());
|
|
file.close();
|
|
SdMan.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) {
|
|
Serial.printf("[%lu] [HTTP] Write failed: wrote %zu of %zu bytes\n", millis(), written, bytesRead);
|
|
file.close();
|
|
SdMan.remove(destPath.c_str());
|
|
http.end();
|
|
return FILE_ERROR;
|
|
}
|
|
|
|
downloaded += bytesRead;
|
|
|
|
if (progress && total > 0) {
|
|
progress(downloaded, total);
|
|
}
|
|
}
|
|
|
|
file.close();
|
|
http.end();
|
|
|
|
Serial.printf("[%lu] [HTTP] Downloaded %zu bytes\n", millis(), downloaded);
|
|
|
|
// Verify download size if known
|
|
if (contentLength > 0 && downloaded != contentLength) {
|
|
Serial.printf("[%lu] [HTTP] Size mismatch: got %zu, expected %zu\n", millis(), downloaded, contentLength);
|
|
SdMan.remove(destPath.c_str());
|
|
return HTTP_ERROR;
|
|
}
|
|
|
|
return OK;
|
|
}
|