fixed basic auth for opds and added more calibre commands
now supports viewing books on device and deleting them
This commit is contained in:
@@ -14,7 +14,7 @@ CrossPointSettings CrossPointSettings::instance;
|
||||
namespace {
|
||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||
// Increment this when adding new persisted settings fields
|
||||
constexpr uint8_t SETTINGS_COUNT = 20;
|
||||
constexpr uint8_t SETTINGS_COUNT = 21;
|
||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ void CalibreConnectActivity::onEnter() {
|
||||
currentUploadName.clear();
|
||||
lastCompleteName.clear();
|
||||
lastCompleteAt = 0;
|
||||
exitRequested = false;
|
||||
|
||||
xTaskCreate(&CalibreConnectActivity::taskTrampoline, "CalibreConnectTask",
|
||||
2048, // Stack size
|
||||
@@ -124,8 +125,7 @@ void CalibreConnectActivity::loop() {
|
||||
}
|
||||
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onComplete();
|
||||
return;
|
||||
exitRequested = true;
|
||||
}
|
||||
|
||||
if (webServer && webServer->isRunning()) {
|
||||
@@ -135,17 +135,17 @@ void CalibreConnectActivity::loop() {
|
||||
}
|
||||
|
||||
esp_task_wdt_reset();
|
||||
constexpr int MAX_ITERATIONS = 500;
|
||||
constexpr int MAX_ITERATIONS = 80;
|
||||
for (int i = 0; i < MAX_ITERATIONS && webServer->isRunning(); i++) {
|
||||
webServer->handleClient();
|
||||
if ((i & 0x1F) == 0x1F) {
|
||||
if ((i & 0x07) == 0x07) {
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
if ((i & 0x3F) == 0x3F) {
|
||||
if ((i & 0x0F) == 0x0F) {
|
||||
yield();
|
||||
if (mappedInput.wasPressed(MappedInputManager::Button::Back)) {
|
||||
onComplete();
|
||||
return;
|
||||
exitRequested = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -181,6 +181,11 @@ void CalibreConnectActivity::loop() {
|
||||
updateRequired = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (exitRequested) {
|
||||
onComplete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void CalibreConnectActivity::displayTaskLoop() {
|
||||
@@ -215,10 +220,14 @@ void CalibreConnectActivity::render() const {
|
||||
|
||||
void CalibreConnectActivity::renderServerRunning() const {
|
||||
constexpr int LINE_SPACING = 24;
|
||||
constexpr int TOP_PADDING = 18;
|
||||
constexpr int SMALL_SPACING = 20;
|
||||
constexpr int SECTION_SPACING = 40;
|
||||
constexpr int TOP_PADDING = 14;
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Connect to Calibre", true, EpdFontFamily::BOLD);
|
||||
|
||||
int y = 60 + TOP_PADDING;
|
||||
int y = 55 + TOP_PADDING;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, "Network", true, EpdFontFamily::BOLD);
|
||||
y += LINE_SPACING;
|
||||
std::string ssidInfo = "Network: " + connectedSSID;
|
||||
if (ssidInfo.length() > 28) {
|
||||
ssidInfo.replace(25, ssidInfo.length() - 25, "...");
|
||||
@@ -226,22 +235,17 @@ void CalibreConnectActivity::renderServerRunning() const {
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, ssidInfo.c_str());
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y + LINE_SPACING, ("IP: " + connectedIP).c_str());
|
||||
|
||||
y += LINE_SPACING * 2;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, "Install the CrossPoint Reader");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + LINE_SPACING, "device plugin in Calibre.");
|
||||
y += LINE_SPACING * 2 + SECTION_SPACING;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, "Setup", true, EpdFontFamily::BOLD);
|
||||
y += LINE_SPACING;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, "1) Install CrossPoint Reader plugin");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING, "2) Be on the same WiFi network");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 2, "3) In Calibre: \"Send to device\"");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + SMALL_SPACING * 3, "Keep this screen open while sending");
|
||||
|
||||
y += LINE_SPACING * 2;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, "Make sure your computer is");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + LINE_SPACING, "on the same WiFi network.");
|
||||
|
||||
y += LINE_SPACING * 2;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, "Then in Calibre, click");
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y + LINE_SPACING, "\"Send to device\".");
|
||||
|
||||
y += LINE_SPACING * 2;
|
||||
renderer.drawCenteredText(SMALL_FONT_ID, y, "Leave this screen open while sending.");
|
||||
|
||||
y += LINE_SPACING * 2;
|
||||
y += SMALL_SPACING * 3 + SECTION_SPACING;
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y, "Status", true, EpdFontFamily::BOLD);
|
||||
y += LINE_SPACING;
|
||||
if (lastProgressTotal > 0 && lastProgressReceived <= lastProgressTotal) {
|
||||
std::string label = "Receiving";
|
||||
if (!currentUploadName.empty()) {
|
||||
@@ -254,9 +258,9 @@ void CalibreConnectActivity::renderServerRunning() const {
|
||||
constexpr int barWidth = 300;
|
||||
constexpr int barHeight = 16;
|
||||
constexpr int barX = (480 - barWidth) / 2;
|
||||
ScreenComponents::drawProgressBar(renderer, barX, y + 28, barWidth, barHeight, lastProgressReceived,
|
||||
ScreenComponents::drawProgressBar(renderer, barX, y + 22, barWidth, barHeight, lastProgressReceived,
|
||||
lastProgressTotal);
|
||||
y += 46;
|
||||
y += 40;
|
||||
}
|
||||
|
||||
if (lastCompleteAt > 0 && (millis() - lastCompleteAt) < 6000) {
|
||||
|
||||
@@ -32,6 +32,7 @@ class CalibreConnectActivity final : public ActivityWithSubactivity {
|
||||
std::string currentUploadName;
|
||||
std::string lastCompleteName;
|
||||
unsigned long lastCompleteAt = 0;
|
||||
bool exitRequested = false;
|
||||
|
||||
static void taskTrampoline(void* param);
|
||||
[[noreturn]] void displayTaskLoop();
|
||||
|
||||
@@ -116,8 +116,8 @@ void CalibreSettingsActivity::handleSelection() {
|
||||
exitActivity();
|
||||
enterNewActivity(new KeyboardEntryActivity(
|
||||
renderer, mappedInput, "Password", SETTINGS.opdsPassword, 10,
|
||||
63, // maxLength
|
||||
true, // password mode
|
||||
63, // maxLength
|
||||
false, // not password mode
|
||||
[this](const std::string& password) {
|
||||
strncpy(SETTINGS.opdsPassword, password.c_str(), sizeof(SETTINGS.opdsPassword) - 1);
|
||||
SETTINGS.opdsPassword[sizeof(SETTINGS.opdsPassword) - 1] = '\0';
|
||||
|
||||
@@ -90,6 +90,7 @@ void CrossPointWebServer::begin() {
|
||||
|
||||
server->on("/api/status", HTTP_GET, [this] { handleStatus(); });
|
||||
server->on("/api/files", HTTP_GET, [this] { handleFileListData(); });
|
||||
server->on("/download", HTTP_GET, [this] { handleDownload(); });
|
||||
|
||||
// Upload endpoint with special handling for multipart form data
|
||||
server->on("/upload", HTTP_POST, [this] { handleUploadPost(); }, [this] { handleUpload(); });
|
||||
@@ -382,6 +383,69 @@ void CrossPointWebServer::handleFileListData() const {
|
||||
Serial.printf("[%lu] [WEB] Served file listing page for path: %s\n", millis(), currentPath.c_str());
|
||||
}
|
||||
|
||||
void CrossPointWebServer::handleDownload() const {
|
||||
if (!server->hasArg("path")) {
|
||||
server->send(400, "text/plain", "Missing path");
|
||||
return;
|
||||
}
|
||||
|
||||
String itemPath = server->arg("path");
|
||||
if (itemPath.isEmpty() || itemPath == "/") {
|
||||
server->send(400, "text/plain", "Invalid path");
|
||||
return;
|
||||
}
|
||||
if (!itemPath.startsWith("/")) {
|
||||
itemPath = "/" + itemPath;
|
||||
}
|
||||
|
||||
const String itemName = itemPath.substring(itemPath.lastIndexOf('/') + 1);
|
||||
if (itemName.startsWith(".")) {
|
||||
server->send(403, "text/plain", "Cannot access system files");
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < HIDDEN_ITEMS_COUNT; i++) {
|
||||
if (itemName.equals(HIDDEN_ITEMS[i])) {
|
||||
server->send(403, "text/plain", "Cannot access protected items");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SdMan.exists(itemPath.c_str())) {
|
||||
server->send(404, "text/plain", "Item not found");
|
||||
return;
|
||||
}
|
||||
|
||||
FsFile file = SdMan.open(itemPath.c_str());
|
||||
if (!file) {
|
||||
server->send(500, "text/plain", "Failed to open file");
|
||||
return;
|
||||
}
|
||||
if (file.isDirectory()) {
|
||||
file.close();
|
||||
server->send(400, "text/plain", "Path is a directory");
|
||||
return;
|
||||
}
|
||||
|
||||
String contentType = "application/octet-stream";
|
||||
if (isEpubFile(itemPath)) {
|
||||
contentType = "application/epub+zip";
|
||||
}
|
||||
|
||||
char nameBuf[128] = {0};
|
||||
String filename = "download";
|
||||
if (file.getName(nameBuf, sizeof(nameBuf))) {
|
||||
filename = nameBuf;
|
||||
}
|
||||
|
||||
server->setContentLength(file.size());
|
||||
server->sendHeader("Content-Disposition", "attachment; filename=\"" + filename + "\"");
|
||||
server->send(200, contentType.c_str(), "");
|
||||
|
||||
WiFiClient client = server->client();
|
||||
client.write(file);
|
||||
file.close();
|
||||
}
|
||||
|
||||
// Static variables for upload handling
|
||||
static FsFile uploadFile;
|
||||
static String uploadFileName;
|
||||
|
||||
@@ -73,6 +73,7 @@ class CrossPointWebServer {
|
||||
void handleStatus() const;
|
||||
void handleFileList() const;
|
||||
void handleFileListData() const;
|
||||
void handleDownload() const;
|
||||
void handleUpload() const;
|
||||
void handleUploadPost() const;
|
||||
void handleCreateFolder() const;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <HardwareSerial.h>
|
||||
#include <WiFiClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <base64.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
@@ -31,7 +32,9 @@ bool HttpDownloader::fetchUrl(const std::string& url, std::string& outContent) {
|
||||
|
||||
// Add Basic HTTP auth if credentials are configured
|
||||
if (strlen(SETTINGS.opdsUsername) > 0 && strlen(SETTINGS.opdsPassword) > 0) {
|
||||
http.setAuthorization(SETTINGS.opdsUsername, SETTINGS.opdsPassword);
|
||||
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();
|
||||
@@ -70,7 +73,9 @@ HttpDownloader::DownloadError HttpDownloader::downloadToFile(const std::string&
|
||||
|
||||
// Add Basic HTTP auth if credentials are configured
|
||||
if (strlen(SETTINGS.opdsUsername) > 0 && strlen(SETTINGS.opdsPassword) > 0) {
|
||||
http.setAuthorization(SETTINGS.opdsUsername, SETTINGS.opdsPassword);
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user