diff --git a/esp32/lib/ESP32-sveltekit/AnalyticsService.h b/esp32/lib/ESP32-sveltekit/AnalyticsService.h index 7841d9c..697a6bc 100644 --- a/esp32/lib/ESP32-sveltekit/AnalyticsService.h +++ b/esp32/lib/ESP32-sveltekit/AnalyticsService.h @@ -14,7 +14,7 @@ **/ #include -#include +#include #include #include #include diff --git a/esp32/lib/ESP32-sveltekit/CameraSettingsService.h b/esp32/lib/ESP32-sveltekit/CameraSettingsService.h index 57052dc..fb8e1c5 100644 --- a/esp32/lib/ESP32-sveltekit/CameraSettingsService.h +++ b/esp32/lib/ESP32-sveltekit/CameraSettingsService.h @@ -12,7 +12,7 @@ namespace Camera { #include #include #include -#include +#include #define EVENT_CAMERA_SETTINGS "CameraSettings" #define CAMERA_SETTINGS_PATH "/api/camera/settings" diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp index a3fa18f..051161b 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.cpp @@ -40,7 +40,6 @@ ESP32SvelteKit::ESP32SvelteKit(PsychicHttpServer *server, unsigned int numberEnd _cameraService(server, &_taskManager), _cameraSettingsService(server, &ESPFS, &_socket), #endif - _fileExplorer(server), _servoController(server, &ESPFS, &_peripherals, &_socket), #if FT_ENABLED(USE_MOTION) _motionService(_server, &_socket, &_servoController, &_taskManager), @@ -112,6 +111,12 @@ void ESP32SvelteKit::setupServer() { _server->on("/api/system/status", HTTP_GET, system_service::getStatus); _server->on("/api/system/metrics", HTTP_GET, system_service::getMetrics); + // FILESYSTEM + _server->on("/api/files", HTTP_GET, FileSystem::getFiles); + _server->on("/api/files/delete", HTTP_POST, FileSystem::handleDelete); + _server->on("/api/files/upload/*", HTTP_POST, FileSystem::uploadHandler); + _server->on("/api/files/edit", HTTP_POST, FileSystem::handleEdit); + // servo _server->on("/api/servo/config", HTTP_GET, [this](PsychicRequest *request) { return _servoController.endpoint.getState(request); }); @@ -202,7 +207,6 @@ void ESP32SvelteKit::startServices() { _batteryService.begin(); #endif _taskManager.begin(); - _fileExplorer.begin(); _peripherals.begin(); _servoController.begin(); #if FT_ENABLED(USE_MOTION) diff --git a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h index a196ea2..5562ddc 100644 --- a/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h +++ b/esp32/lib/ESP32-sveltekit/ESP32SvelteKit.h @@ -20,11 +20,10 @@ #include #include -#include +#include #include #include #include -#include #include #include #include @@ -81,8 +80,6 @@ class ESP32SvelteKit { TaskManager *getTaskManager() { return &_taskManager; } - FileExplorer *getFileExplorer() { return &_fileExplorer; } - #if FT_ENABLED(USE_MOTION) MotionService *getMotionService() { return &_motionService; } #endif @@ -127,7 +124,6 @@ class ESP32SvelteKit { AnalyticsService _analyticsService; #endif TaskManager _taskManager; - FileExplorer _fileExplorer; #if FT_ENABLED(USE_MOTION) MotionService _motionService; #endif diff --git a/esp32/lib/ESP32-sveltekit/ESPFS.h b/esp32/lib/ESP32-sveltekit/ESPFS.h deleted file mode 100644 index 0db8821..0000000 --- a/esp32/lib/ESP32-sveltekit/ESPFS.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include - -#define ESPFS LittleFS - -#define AP_SETTINGS_FILE "/config/apSettings.json" -#define CAMERA_SETTINGS_FILE "/config/cameraSettings.json" -#define FS_CONFIG_DIRECTORY "/config" -#define NTP_SETTINGS_FILE "/config/ntpSettings.json" -#define DEVICE_CONFIG_FILE "/config/peripheral.json" -#define WIFI_SETTINGS_FILE "/config/wifiSettings.json" -#define SERVO_SETTINGS_FILE "/config/servoSettings.json" \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/FSPersistence.h b/esp32/lib/ESP32-sveltekit/FSPersistence.h index e745995..0e940d7 100644 --- a/esp32/lib/ESP32-sveltekit/FSPersistence.h +++ b/esp32/lib/ESP32-sveltekit/FSPersistence.h @@ -18,7 +18,7 @@ #include #include -#include +#include template class FSPersistence { @@ -32,6 +32,7 @@ class FSPersistence { _filePath(filePath), _updateHandlerId(0) { enableUpdateHandler(); + readFromFS(); } void readFromFS() { diff --git a/esp32/lib/ESP32-sveltekit/FileExplorerService.h b/esp32/lib/ESP32-sveltekit/FileExplorerService.h deleted file mode 100644 index cda8745..0000000 --- a/esp32/lib/ESP32-sveltekit/FileExplorerService.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef FileExplorer_h -#define FileExplorer_h - -#include -#include - -#define FILE_EXPLORER_SERVICE_PATH "/api/files" -#define FILE_EXPLORER_DELETE_SERVICE_PATH "/api/files/delete" - -class FileExplorer { - public: - FileExplorer(PsychicHttpServer *server) : _server(server) {} - - void begin() { - _server->on(FILE_EXPLORER_SERVICE_PATH, HTTP_GET, [this](PsychicRequest *request) { return explore(request); }); - _server->on(FILE_EXPLORER_DELETE_SERVICE_PATH, HTTP_POST, - [this](PsychicRequest *request, JsonVariant &json) { return deleteFile(request, json); }); - - ESP_LOGV("APStatus", "Registered GET endpoint: %s", FILE_EXPLORER_SERVICE_PATH); - } - - private: - PsychicHttpServer *_server; - - esp_err_t explore(PsychicRequest *request) { - return request->reply(200, "application/json", listFiles("/").c_str()); - } - - esp_err_t deleteFile(PsychicRequest *request, JsonVariant &json) { - if (json.is()) { - String filename = json["file"]; - ESP_LOGI("FileExplorer", "Deleting file: %s", filename.c_str()); - return ESPFS.remove(filename.c_str()) ? request->reply(200) : request->reply(500); - } - return request->reply(400); - } - - String listFiles(const String &directory, bool isRoot = true) { - File root = ESPFS.open(directory.startsWith("/") ? directory : "/" + directory); - if (!root.isDirectory()) { - return ""; - } - - File file = root.openNextFile(); - String output = isRoot ? "{ \"root\": {" : "{"; - - while (file) { - if (file.isDirectory()) { - output += "\"" + String(file.name()) + "\": " + listFiles(file.name(), false) + ", "; - } else { - output += "\"" + String(file.name()) + "\": " + String(file.size()) + ", "; - } - file = root.openNextFile(); - } - - if (output.endsWith(", ")) { - output.remove(output.length() - 2); - } - output += "}"; - if (isRoot) { - output += "}"; - } - return output; - } -}; - -#endif // end FileExplorer_h diff --git a/esp32/lib/ESP32-sveltekit/Peripherals.h b/esp32/lib/ESP32-sveltekit/Peripherals.h index 29ae42f..402a7b0 100644 --- a/esp32/lib/ESP32-sveltekit/Peripherals.h +++ b/esp32/lib/ESP32-sveltekit/Peripherals.h @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include diff --git a/esp32/lib/ESP32-sveltekit/filesystem.cpp b/esp32/lib/ESP32-sveltekit/filesystem.cpp new file mode 100644 index 0000000..9e426ef --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/filesystem.cpp @@ -0,0 +1,101 @@ +#include + +static const char *TAG = "FileService"; + +namespace FileSystem { + +PsychicUploadHandler *uploadHandler; + +class Initializer { + public: + Initializer() { + uploadHandler = new PsychicUploadHandler(); + uploadHandler->onUpload(uploadFile); + uploadHandler->onRequest([](PsychicRequest *request) { return request->reply(200); }); + } +}; + +static Initializer initializer; + +esp_err_t getFiles(PsychicRequest *request) { return request->reply(200, "application/json", listFiles("/").c_str()); } + +esp_err_t handleDelete(PsychicRequest *request, JsonVariant &json) { + if (json.is()) { + const char *filename = json["file"].as(); + ESP_LOGI(TAG, "Deleting file: %s", filename); + return deleteFile(filename) ? request->reply(200) : request->reply(500); + } + return request->reply(400); +} + +esp_err_t handleEdit(PsychicRequest *request, JsonVariant &json) { + if (json.is()) { + const char *filename = json["file"].as(); + const char *content = json["content"].as(); + ESP_LOGI(TAG, "Editing file: %s", filename); + return editFile(filename, content) ? request->reply(200) : request->reply(500); + } + return request->reply(400); +} + +/* Helpers */ + +bool deleteFile(const char *filename) { return ESPFS.remove(filename); } + +String listFiles(const String &directory, bool isRoot) { + File root = ESPFS.open(directory.startsWith("/") ? directory : "/" + directory); + if (!root.isDirectory()) return ""; + + File file = root.openNextFile(); + String output = isRoot ? "{ \"root\": {" : "{"; + + while (file) { + if (file.isDirectory()) { + output += "\"" + String(file.name()) + "\": " + listFiles(file.name(), false) + ", "; + } else { + output += "\"" + String(file.name()) + "\": " + String(file.size()) + ", "; + } + file = root.openNextFile(); + } + + if (output.endsWith(", ")) { + output.remove(output.length() - 2); + } + output += "}"; + if (isRoot) output += "}"; + + return output; +} + +esp_err_t uploadFile(PsychicRequest *request, const String &filename, uint64_t index, uint8_t *data, size_t len, + bool last) { + File file; + String path = "/www/" + filename; + ESP_LOGI(TAG, "Writing %d/%d bytes to: %s\n", (int)index + (int)len, request->contentLength(), path.c_str()); + + if (last) ESP_LOGI(TAG, "%s is finished. Total bytes: %d\n", path.c_str(), (int)index + (int)len); + + file = LittleFS.open(path, !index ? FILE_WRITE : FILE_APPEND); + if (!file) { + ESP_LOGE(TAG, "Failed to open file"); + return ESP_FAIL; + } + + if (!file.write(data, len)) { + ESP_LOGE(TAG, "Write failed"); + return ESP_FAIL; + } + + return ESP_OK; +} + +bool editFile(const char *filename, const char *content) { + File file = ESPFS.open(filename, FILE_WRITE); + if (!file) return false; + + file.print(content); + file.close(); + return true; +} + +} // namespace FileSystem \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/filesystem.h b/esp32/lib/ESP32-sveltekit/filesystem.h new file mode 100644 index 0000000..87a0c0b --- /dev/null +++ b/esp32/lib/ESP32-sveltekit/filesystem.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +#define ESPFS LittleFS + +#define AP_SETTINGS_FILE "/config/apSettings.json" +#define CAMERA_SETTINGS_FILE "/config/cameraSettings.json" +#define FS_CONFIG_DIRECTORY "/config" +#define NTP_SETTINGS_FILE "/config/ntpSettings.json" +#define DEVICE_CONFIG_FILE "/config/peripheral.json" +#define WIFI_SETTINGS_FILE "/config/wifiSettings.json" +#define SERVO_SETTINGS_FILE "/config/servoSettings.json" + +namespace FileSystem { +extern PsychicUploadHandler *uploadHandler; + +String listFiles(const String &directory, bool isRoot = true); +bool deleteFile(const char *filename); +bool editFile(const char *filename, const char *content); +esp_err_t uploadFile(PsychicRequest *request, const String &filename, uint64_t index, uint8_t *data, size_t len, + bool last); + +esp_err_t getFiles(PsychicRequest *request); +esp_err_t handleDelete(PsychicRequest *request, JsonVariant &json); +esp_err_t handleEdit(PsychicRequest *request, JsonVariant &json); +} // namespace FileSystem \ No newline at end of file diff --git a/esp32/lib/ESP32-sveltekit/ntp_service.h b/esp32/lib/ESP32-sveltekit/ntp_service.h index 3ce6fe5..c030782 100644 --- a/esp32/lib/ESP32-sveltekit/ntp_service.h +++ b/esp32/lib/ESP32-sveltekit/ntp_service.h @@ -1,7 +1,7 @@ #ifndef NTPService_h #define NTPService_h -#include +#include #include #include #include diff --git a/esp32/lib/ESP32-sveltekit/system_service.h b/esp32/lib/ESP32-sveltekit/system_service.h index 3ca8265..ac9438e 100644 --- a/esp32/lib/ESP32-sveltekit/system_service.h +++ b/esp32/lib/ESP32-sveltekit/system_service.h @@ -1,7 +1,7 @@ #ifndef SYSTEM_SERVICE_H #define SYSTEM_SERVICE_H -#include +#include #include #include #include diff --git a/esp32/lib/ESP32-sveltekit/wifi_service.h b/esp32/lib/ESP32-sveltekit/wifi_service.h index b2017de..4f33f8d 100644 --- a/esp32/lib/ESP32-sveltekit/wifi_service.h +++ b/esp32/lib/ESP32-sveltekit/wifi_service.h @@ -5,7 +5,7 @@ #include #include -#include +#include #include #include #include