#include #include "FilesystemAbstraction.h" #define FLASH_TEXT(name) const char *name namespace webdav_constants { FLASH_TEXT(MULTISTATUS_START) = ""; FLASH_TEXT(MULTISTATUS_END) = ""; FLASH_TEXT(RESPONSE_START) = ""; FLASH_TEXT(RESPONSE_END) = "\n"; FLASH_TEXT(HREF_START) = ""; FLASH_TEXT(HREF_END) = ""; FLASH_TEXT(PROPSTAT_START) = ""; FLASH_TEXT(PROPSTAT_END) = ""; FLASH_TEXT(PROP_START) = ""; FLASH_TEXT(PROP_END) = ""; FLASH_TEXT(RESOURCETYPE_START) = ""; FLASH_TEXT(RESOURCETYPE_END) = ""; FLASH_TEXT(RESOURCE_COLLECTION) = ""; FLASH_TEXT(HTTP_204_NO_CONTENT) = "HTTP/1.1 204 No Content"; FLASH_TEXT(CONTENTLEN_START) = ""; FLASH_TEXT(CONTENTLEN_END) = ""; FLASH_TEXT(CREATEDATE_START) = ""; FLASH_TEXT(CREATEDATE_END) = ""; FLASH_TEXT(MODDATE_START) = ""; FLASH_TEXT(MODDATE_END) = ""; FLASH_TEXT(STATUS_OK) = "HTTP/1.1 200 OK"; } // namespace webdav_constants class WebdavFileListCallback { public: WebdavFileListCallback(const String &path) : path_(path), headerWritten_(false), finished_(false) { dir_ = portablefs::openDir(path); } size_t operator()(uint8_t *buffer, size_t maxLen, size_t index) { Serial.print("index "); Serial.println(index); using namespace webdav_constants; uint8_t *bufferStart = buffer; if (finished_) return 0; if (!headerWritten_) { toBuffer(buffer, MULTISTATUS_START); headerWritten_ = true; } bool fileFound = false; while (dir_.next()) { if (isFirstFileOfTrainingGroup()) { fileFound = true; break; } } if (fileFound) { //toBuffer(buffer, path_.c_str()); toBuffer(buffer, RESPONSE_START); toBuffer(buffer, HREF_START); const auto fileName = dir_.fileName(); const auto fileNameWithoutDir = fileName.substring(fileName.lastIndexOf("/") + 1); String fileBaseName = fileNameWithoutDir.substring(0, fileNameWithoutDir.indexOf('_')); fileBaseName += ".st"; toBuffer(buffer, fileBaseName.c_str()); toBuffer(buffer, HREF_END); toBuffer(buffer, PROPSTAT_START); toBuffer(buffer, PROP_START); if (dir_.isDirectory()) { toBuffer(buffer, RESOURCETYPE_START); toBuffer(buffer, RESOURCE_COLLECTION); toBuffer(buffer, RESOURCETYPE_END); } else { toBuffer(buffer, CONTENTLEN_START); String fileSizeStr(getFileSize(fileName)); toBuffer(buffer, fileSizeStr.c_str()); toBuffer(buffer, CONTENTLEN_END); } toBuffer(buffer, PROP_END); toBuffer(buffer, STATUS_OK); toBuffer(buffer, PROPSTAT_END); toBuffer(buffer, webdav_constants::RESPONSE_END); } else { toBuffer(buffer, MULTISTATUS_END); finished_ = true; } size_t bytesWritten = buffer - bufferStart; assert_msg(bytesWritten < maxLen, "Written too much!"); //Serial.print("Bytes written "); //Serial.println(bytesWritten); //Serial.print("Max bytes "); //Serial.println(maxLen); return bytesWritten; } private: bool isFirstFileOfTrainingGroup() { return !dir_.isDirectory() && dir_.fileName().endsWith("_0"); } size_t getFileSize(const String &fileZero) { size_t size = 0; auto fileBase = fileZero.substring(0, fileZero.indexOf('_')); auto newDirInstance = portablefs::openDir(path_); while (newDirInstance.next()) if (newDirInstance.isFile() && newDirInstance.fileName().startsWith(fileBase)) size += newDirInstance.fileSize(); return size; } void toBuffer(uint8_t *&buffer, const char *text) { auto len = strlen(text); memcpy(buffer, text, len); buffer += len; } portablefs::Dir dir_; const String path_; bool headerWritten_; bool finished_; }; bool deleteMeasurementFiles(const String &stName, const String &folder) { String baseName = folder + "/" + stName.substring(0, stName.indexOf(".")); int counter = 0; { auto d = portablefs::openDir(folder); while (d.next()) if (d.isFile() && d.fileName().startsWith(baseName)) ++counter; } if (counter == 0) return false; for (int i = 0; i < counter; ++i) { const String pathToDelete = baseName + "_" + String(i); if (!SPIFFS.remove(pathToDelete)) return false; } return true; } class SpiffsWebDavHandler : public AsyncWebHandler { public: SpiffsWebDavHandler(const String &prefix, const String &folder) : prefix_(prefix), folder_(folder) { } virtual bool canHandle(AsyncWebServerRequest *request) override final { Serial.print("Can handle for url : "); Serial.println(request->url()); return request->url().startsWith(prefix_); } virtual void handleRequest(AsyncWebServerRequest *request) override final { if (request->url() == prefix_ + "/" && (request->method() == HTTP_GET || request->method() == HTTP_PROPFIND)) { // send chunked response - it is too large to send in one go auto response = request->beginChunkedResponse("application/xml", WebdavFileListCallback(folder_)); request->send(response); } /* else if(request->url() == prefix_ + "/" && request->method() == HTTP_GET) { AsyncResponseStream * response = request->beginResponseStream("text/plain", 1460*10); Dir dir = SPIFFS.openDir(folder_); Serial.print("Opening folder "); Serial.println(folder_); while (dir.next()) { Serial.print(" File: "); Serial.println(dir.fileName()); response->println(dir.fileName()); } request->send(response); }*/ else if (request->method() == HTTP_GET) { auto path = folder_ + request->url().substring(prefix_.length()); if (SPIFFS.exists(path)) request->send(SPIFFS, path, "application/x-msgpack"); else request->send(404, "text/plain", "Webdav: File not found"); } else if (request->method() == HTTP_DELETE) { auto stFileName = request->url().substring(prefix_.length() + 1); Serial.print("HTTP_DELETE for "); Serial.println(stFileName); bool deleteSuccessful = deleteMeasurementFiles(stFileName, folder_); if (deleteSuccessful) request->send(204, "text/plain", "Success"); else request->send(404, "text/plain", "Webdav: File to delete not found"); } else { request->send(404, "text/plain", "Webdav: Invalid request"); } } virtual bool isRequestHandlerTrivial() override final { return false; } private: String prefix_; String folder_; };