diff --git a/CMakeLists.txt b/CMakeLists.txt index b833856..c8a30b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,9 @@ idf_component_register( SRCS "src/SystemConfiguration.c" "src/romfs.c" + "src/spifs.c" "src/HTTPServer.c" + "src/FileServer.c" "src/HTTPPrintSystem.c" "src/HTTPPostSystem.c" "src/Helpers.c" @@ -17,9 +19,12 @@ idf_component_register( INCLUDE_DIRS "." "include" "src" + + EMBED_FILES "upload_script.html" REQUIRES nvs_flash libespfs + spiffs esp_http_server mbedtls lwip diff --git a/include/HTTPServer.h b/include/HTTPServer.h index b538c32..6257e16 100644 --- a/include/HTTPServer.h +++ b/include/HTTPServer.h @@ -47,6 +47,15 @@ #define MAX_DYNVAR_LENGTH 256 #define MAX_INCFILE_LENGTH 1024 +/* Max length a file path can have on storage */ +#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN) +#define MAX_FILE_SIZE (200*1024) // 200 KB +#define MAX_FILE_SIZE_STR "200KB" + +/* Scratch buffer size */ +#define SCRATCH_BUFSIZE 4096 +#define AUTH_DATA_MAX_LENGTH 16 + #define HTTP_SERVER_DEBUG_LEVEL 0 typedef enum @@ -58,6 +67,18 @@ typedef enum HTTP_IO_DONE_NOREFRESH } HTTP_IO_RESULT; + + +struct file_server_data +{ + /* Base path of file storage */ + char base_path[ESP_VFS_PATH_MAX + 1]; + char base_path2[ESP_VFS_PATH_MAX + 1]; + /* Scratch buffer for temporary storage during file transfer */ + char scratch[SCRATCH_BUFSIZE]; +/* Pointer to external POST handler*/ +}; + typedef struct { const char tag[16]; @@ -72,4 +93,9 @@ esp_err_t start_file_server(void); HTTP_IO_RESULT HTTPPostApp(httpd_req_t *req, const char *filename, char *PostData); int HTTPPrint(httpd_req_t *req, char* buf, char* var); +esp_err_t download_get_handler(httpd_req_t *req); +esp_err_t upload_post_handler(httpd_req_t *req); +esp_err_t delete_post_handler(httpd_req_t *req); + + #endif /* COMPONENTS_WEB_GUI_APPLICATION_INCLUDE_HTTPSERVER_H_ */ diff --git a/include/spifs.h b/include/spifs.h new file mode 100644 index 0000000..20d2c49 --- /dev/null +++ b/include/spifs.h @@ -0,0 +1,33 @@ + /*! Copyright 2022 Bogdan Pilyugin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * \file spifs.h + * \version 1.0 + * \date 2022-11-13 + * \author Bogdan Pilyugin + * \brief + * \details + * \copyright Apache License, Version 2.0 + */ + +#ifndef COMPONENTS_WEBGUIAPP_INCLUDE_SPIFS_H_ +#define COMPONENTS_WEBGUIAPP_INCLUDE_SPIFS_H_ + +#include "esp_err.h" + +esp_err_t init_spi_fs(const char *root); + + + +#endif /* COMPONENTS_WEBGUIAPP_INCLUDE_SPIFS_H_ */ diff --git a/src/FileServer.c b/src/FileServer.c new file mode 100644 index 0000000..74daa88 --- /dev/null +++ b/src/FileServer.c @@ -0,0 +1,476 @@ +/*! Copyright 2022 Bogdan Pilyugin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * \file FileServer.c + * \version 1.0 + * \date 2022-11-13 + * \author Bogdan Pilyugin + * \brief + * \details + * \copyright Apache License, Version 2.0 + */ + +#include "HTTPServer.h" + +static const char *TAG = "FileServer"; + +/* Handler to redirect incoming GET request for /index.html to / + * This can be overridden by uploading file with same name */ +static esp_err_t index_html_get_handler(httpd_req_t *req) +{ + httpd_resp_set_status(req, "307 Temporary Redirect"); + httpd_resp_set_hdr(req, "Location", "/"); + httpd_resp_send(req, NULL, 0); // Response body can be empty + return ESP_OK; +} + +#define IS_FILE_EXT(filename, ext) \ + (strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0) + +/* Set HTTP response content type according to file extension */ +static esp_err_t set_content_type_from_file(httpd_req_t *req, + const char *filename) +{ + if (IS_FILE_EXT(filename, ".pdf")) + { + return httpd_resp_set_type(req, "application/pdf"); + } + else if (IS_FILE_EXT(filename, ".html")) + { + return httpd_resp_set_type(req, "text/html"); + } + else if (IS_FILE_EXT(filename, ".jpeg")) + { + return httpd_resp_set_type(req, "image/jpeg"); + } + else if (IS_FILE_EXT(filename, ".png")) + { + return httpd_resp_set_type(req, "image/png"); + } + else if (IS_FILE_EXT(filename, ".ico")) + { + return httpd_resp_set_type(req, "image/x-icon"); + } + else if (IS_FILE_EXT(filename, ".css")) + { + return httpd_resp_set_type(req, "text/css"); + } + else if (IS_FILE_EXT(filename, ".woff2")) + { + return httpd_resp_set_type(req, "font/woff2"); + } + /* This is a limited set only */ + /* For any other type always set as plain text */ + return httpd_resp_set_type(req, "text/plain"); +} + +/* Copies the full path into destination buffer and returns + * pointer to path (skipping the preceding base path) */ +static const char* get_path_from_uri(char *dest, const char *base_path, + const char *uri, + size_t destsize) +{ + const size_t base_pathlen = strlen(base_path); + size_t pathlen = strlen(uri); + + const char *quest = strchr(uri, '?'); + if (quest) + { + pathlen = MIN(pathlen, quest - uri); + } + const char *hash = strchr(uri, '#'); + if (hash) + { + pathlen = MIN(pathlen, hash - uri); + } + + if (base_pathlen + pathlen + 1 > destsize) + { + /* Full path string won't fit into destination buffer */ + return NULL; + } + + /* Construct full path (base + path) */ + strcpy(dest, base_path); + strlcpy(dest + base_pathlen, uri + (sizeof("/files")-1), pathlen + 1 - (sizeof("/files")-1)); + + /* Return pointer to path, skipping the base */ + return dest + base_pathlen; +} + +/* Send HTTP response with a run-time generated html consisting of + * a list of all files and folders under the requested path. + * In case of SPIFFS this returns empty list when path is any + * string other than '/', since SPIFFS doesn't support directories */ +static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath) +{ + char entrypath[FILE_PATH_MAX]; + char entrysize[16]; + const char *entrytype; + + struct dirent *entry; + struct stat entry_stat; + + DIR *dir = opendir(dirpath); + const size_t dirpath_len = strlen(dirpath); + + /* Retrieve the base path of file storage to construct the full path */ + strlcpy(entrypath, dirpath, sizeof(entrypath)); + + if (!dir) + { + ESP_LOGE(TAG, "Failed to stat dir : %s", dirpath); + /* Respond with 404 Not Found */ + httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Files directory does not exist"); + return ESP_FAIL; + } + + /* Send HTML file header */ + httpd_resp_sendstr_chunk(req, ""); + + /* Get handle to embedded file upload script */ + extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start"); + extern const unsigned char upload_script_end[] asm("_binary_upload_script_html_end"); + const size_t upload_script_size = (upload_script_end - upload_script_start); + + /* Add file upload form and script which on execution sends a POST request to /upload */ + httpd_resp_send_chunk(req, (const char*) upload_script_start, upload_script_size); + + /* Send file-list table definition and column labels */ + httpd_resp_sendstr_chunk( + req, + "" + "" + "" + ""); + + /* Iterate over all files / folders and fetch their names and sizes */ + while ((entry = readdir(dir)) != NULL) + { + entrytype = (entry->d_type == DT_DIR ? "directory" : "file"); + + strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len); + if (stat(entrypath, &entry_stat) == -1) + { + ESP_LOGE(TAG, "Failed to stat %s : %s", entrytype, entry->d_name); + continue; + } + sprintf(entrysize, "%ld", entry_stat.st_size); + ESP_LOGI(TAG, "Found %s : %s (%s bytes)", entrytype, entry->d_name, entrysize); + + /* Send chunk of HTML file containing table entries with file name and size */ + httpd_resp_sendstr_chunk(req, "\n"); + } + closedir(dir); + + /* Finish the file list table */ + httpd_resp_sendstr_chunk(req, "
NameTypeSize (Bytes)Delete
uri); + httpd_resp_sendstr_chunk(req, entry->d_name); + if (entry->d_type == DT_DIR) + { + httpd_resp_sendstr_chunk(req, "/"); + } + httpd_resp_sendstr_chunk(req, "\">"); + httpd_resp_sendstr_chunk(req, entry->d_name); + httpd_resp_sendstr_chunk(req, ""); + httpd_resp_sendstr_chunk(req, entrytype); + httpd_resp_sendstr_chunk(req, ""); + httpd_resp_sendstr_chunk(req, entrysize); + httpd_resp_sendstr_chunk(req, ""); + httpd_resp_sendstr_chunk(req, "
uri); + httpd_resp_sendstr_chunk(req, entry->d_name); + httpd_resp_sendstr_chunk(req, "\">
"); + httpd_resp_sendstr_chunk(req, "
"); + + /* Send remaining chunk of HTML file to complete it */ + httpd_resp_sendstr_chunk(req, ""); + + /* Send empty chunk to signal HTTP response completion */ + httpd_resp_sendstr_chunk(req, NULL); + return ESP_OK; +} + +/* Handler to download a file kept on the server */ +esp_err_t download_get_handler(httpd_req_t *req) +{ + char filepath[FILE_PATH_MAX]; + FILE *fd = NULL; + struct stat file_stat; + + const char *filename = get_path_from_uri(filepath, ((struct file_server_data*) req->user_ctx)->base_path2, + req->uri, + sizeof(filepath)); + ESP_LOGW(TAG, "FILE_GET_URI %s", req->uri); + ESP_LOGW(TAG, "FILE_GET_FILEPATH %s", filepath); + ESP_LOGW(TAG, "FILE_GET_FILENAME %s", filename); + + if (!filename) + { + ESP_LOGE(TAG, "Filename is too long"); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long"); + return ESP_FAIL; + } + + /* If name has trailing '/', respond with directory contents */ + if (filename[strlen(filename) - 1] == '/') + { + return http_resp_dir_html(req, filepath); + } + + if (stat(filepath, &file_stat) == -1) + { + /* If file not present on SPIFFS check if URI + * corresponds to one of the hardcoded paths */ + + if (strcmp(filename, "/") == 0) + { + return index_html_get_handler(req); + } + + ESP_LOGE(TAG, "Failed to stat file : %s", filepath); + /* Respond with 404 Not Found */ + httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist"); + return ESP_FAIL; + } + + fd = fopen(filepath, "r"); + if (!fd) + { + ESP_LOGE(TAG, "Failed to read existing file : %s", filepath); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filename, file_stat.st_size); + set_content_type_from_file(req, filename); + + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *chunk = ((struct file_server_data*) req->user_ctx)->scratch; + size_t chunksize; + do + { + /* Read file in chunks into the scratch buffer */ + chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd); + + if (chunksize > 0) + { + /* Send the buffer contents as HTTP response chunk */ + if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) + { + fclose(fd); + ESP_LOGE(TAG, "File sending failed!"); + /* Abort sending file */ + httpd_resp_sendstr_chunk(req, NULL); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file"); + return ESP_FAIL; + } + } + + /* Keep looping till the whole file is sent */ + } + while (chunksize != 0); + + /* Close file after sending complete */ + fclose(fd); + ESP_LOGI(TAG, "File sending complete"); + + /* Respond with an empty chunk to signal HTTP response completion */ +#ifdef CONFIG_EXAMPLE_HTTPD_CONN_CLOSE_HEADER + httpd_resp_set_hdr(req, "Connection", "close"); +#endif + httpd_resp_send_chunk(req, NULL, 0); + return ESP_OK; +} + +/* Handler to upload a file onto the server */ +esp_err_t upload_post_handler(httpd_req_t *req) +{ + char filepath[FILE_PATH_MAX]; + FILE *fd = NULL; + struct stat file_stat; + + /* Skip leading "/upload" from URI to get filename */ + /* Note sizeof() counts NULL termination hence the -1 */ + const char *filename = get_path_from_uri(filepath, ((struct file_server_data*) req->user_ctx)->base_path2, + req->uri + sizeof("/upload") - 1, + sizeof(filepath)); + ESP_LOGW(TAG, "FILE_POST_URI %s", req->uri); + ESP_LOGW(TAG, "FILE_POST_FILEPATH %s", filepath); + ESP_LOGW(TAG, "FILE_POST_FILENAME %s", filename); + + if (!filename) + { + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long"); + return ESP_FAIL; + } + + /* Filename cannot have a trailing '/' */ + if (filename[strlen(filename) - 1] == '/') + { + ESP_LOGE(TAG, "Invalid filename : %s", filename); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename"); + return ESP_FAIL; + } + + if (stat(filepath, &file_stat) == 0) + { + ESP_LOGE(TAG, "File already exists : %s", filepath); + /* Respond with 400 Bad Request */ + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File already exists"); + return ESP_FAIL; + } + + /* File cannot be larger than a limit */ + if (req->content_len > MAX_FILE_SIZE) + { + ESP_LOGE(TAG, "File too large : %d bytes", req->content_len); + /* Respond with 400 Bad Request */ + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, + "File size must be less than " + MAX_FILE_SIZE_STR "!"); + /* Return failure to close underlying connection else the + * incoming file content will keep the socket busy */ + return ESP_FAIL; + } + + fd = fopen(filepath, "w"); + if (!fd) + { + ESP_LOGE(TAG, "Failed to create file : %s", filepath); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Receiving file : %s...", filename); + + /* Retrieve the pointer to scratch buffer for temporary storage */ + char *buf = ((struct file_server_data*) req->user_ctx)->scratch; + int received; + + /* Content length of the request gives + * the size of the file being uploaded */ + int remaining = req->content_len; + + while (remaining > 0) + { + + ESP_LOGI(TAG, "Remaining size : %d", remaining); + /* Receive the file part by part into a buffer */ + if ((received = httpd_req_recv(req, buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) + { + if (received == HTTPD_SOCK_ERR_TIMEOUT) + { + /* Retry if timeout occurred */ + continue; + } + + /* In case of unrecoverable error, + * close and delete the unfinished file*/ + fclose(fd); + unlink(filepath); + + ESP_LOGE(TAG, "File reception failed!"); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file"); + return ESP_FAIL; + } + + /* Write buffer content to file on storage */ + if (received && (received != fwrite(buf, 1, received, fd))) + { + /* Couldn't write everything to file! + * Storage may be full? */ + fclose(fd); + unlink(filepath); + + ESP_LOGE(TAG, "File write failed!"); + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write file to storage"); + return ESP_FAIL; + } + + /* Keep track of remaining size of + * the file left to be uploaded */ + remaining -= received; + } + + /* Close file upon upload completion */ + fclose(fd); + ESP_LOGI(TAG, "File reception complete"); + + /* Redirect onto root to see the updated file list */ + httpd_resp_set_status(req, "303 See Other"); + httpd_resp_set_hdr(req, "Location", "/files/"); +#ifdef CONFIG_EXAMPLE_HTTPD_CONN_CLOSE_HEADER + httpd_resp_set_hdr(req, "Connection", "close"); +#endif + httpd_resp_sendstr(req, "File uploaded successfully"); + return ESP_OK; +} + +/* Handler to delete a file from the server */ +esp_err_t delete_post_handler(httpd_req_t *req) +{ + char filepath[FILE_PATH_MAX]; + struct stat file_stat; + + /* Skip leading "/delete" from URI to get filename */ + /* Note sizeof() counts NULL termination hence the -1 */ + const char *filename = get_path_from_uri(filepath, ((struct file_server_data*) req->user_ctx)->base_path2, + req->uri + sizeof("/delete") - 1, + sizeof(filepath)); + if (!filename) + { + /* Respond with 500 Internal Server Error */ + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long"); + return ESP_FAIL; + } + + /* Filename cannot have a trailing '/' */ + if (filename[strlen(filename) - 1] == '/') + { + ESP_LOGE(TAG, "Invalid filename : %s", filename); + httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename"); + return ESP_FAIL; + } + + if (stat(filepath, &file_stat) == -1) + { + ESP_LOGE(TAG, "File does not exist : %s", filename); + /* Respond with 400 Bad Request */ + httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File does not exist"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "Deleting file : %s", filename); + /* Delete file */ + unlink(filepath); + + /* Redirect onto root to see the updated file list */ + httpd_resp_set_status(req, "303 See Other"); + httpd_resp_set_hdr(req, "Location", "/files/"); +#ifdef CONFIG_EXAMPLE_HTTPD_CONN_CLOSE_HEADER + httpd_resp_set_hdr(req, "Connection", "close"); +#endif + httpd_resp_sendstr(req, "File deleted successfully"); + return ESP_OK; +} + diff --git a/src/HTTPServer.c b/src/HTTPServer.c index d617898..7ac9ad5 100644 --- a/src/HTTPServer.c +++ b/src/HTTPServer.c @@ -29,24 +29,6 @@ const char GZIP_SIGN[] = { 0x1f, 0x8b, 0x08 }; static esp_err_t GETHandler(httpd_req_t *req); static esp_err_t CheckAuth(httpd_req_t *req); -/* Max length a file path can have on storage */ -#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN) - -/* Max size of an individual file. Make sure this - * value is same as that set in upload_script.html */ - -/* Scratch buffer size */ -#define SCRATCH_BUFSIZE 4096 -#define AUTH_DATA_MAX_LENGTH 16 - -struct file_server_data -{ - /* Base path of file storage */ - char base_path[ESP_VFS_PATH_MAX + 1]; - /* Scratch buffer for temporary storage during file transfer */ - char scratch[SCRATCH_BUFSIZE]; -/* Pointer to external POST handler*/ -}; struct file_server_data *server_data = NULL; httpd_handle_t server = NULL; static const char *TAG = "HTTPServer"; @@ -190,6 +172,13 @@ static esp_err_t POSTHandler(httpd_req_t *req) #if HTTP_SERVER_DEBUG_LEVEL > 0 ESP_LOGI(TAG, "POST request handle"); #endif + + if (memmem(req->uri, strlen(req->uri), "/files/upload/", sizeof("/files/upload/")-1)) + return upload_post_handler(req); + + if (memmem(req->uri, strlen(req->uri), "/files/delete/", sizeof("/files/delete/")-1)) + return delete_post_handler(req); + char *buf = ((struct file_server_data*) req->user_ctx)->scratch; int received; int remaining = req->content_len; @@ -222,10 +211,12 @@ static esp_err_t POSTHandler(httpd_req_t *req) if (received) { char filepath[FILE_PATH_MAX]; - const char *filename = get_path_from_uri(filepath, - ((struct file_server_data*) req->user_ctx)->base_path, - req->uri, - sizeof(filepath)); + const char *filename; + + filename = get_path_from_uri(filepath, + ((struct file_server_data*) req->user_ctx)->base_path, + req->uri, + sizeof(filepath)); if (!memcmp(filename, "/api", 4)) { @@ -270,6 +261,11 @@ static esp_err_t GETHandler(httpd_req_t *req) #if HTTP_SERVER_DEBUG_LEVEL > 0 ESP_LOGI(TAG, "GET request handle"); #endif + + //Route to file server GET handler + if (memmem(req->uri, strlen(req->uri), "/files/", sizeof("/files/") - 1)) + return download_get_handler(req); + char filepath[FILE_PATH_MAX]; espfs_file_t *file; struct espfs_stat_t stat; @@ -533,6 +529,7 @@ esp_err_t start_file_server(void) return ESP_ERR_NO_MEM; } strlcpy(server_data->base_path, "/", sizeof("/")); + strlcpy(server_data->base_path2, "/data", sizeof("/data")); server = start_webserver(); return ESP_OK; } diff --git a/src/SystemConfiguration.c b/src/SystemConfiguration.c index 42c1fb9..18a2479 100644 --- a/src/SystemConfiguration.c +++ b/src/SystemConfiguration.c @@ -36,6 +36,7 @@ #include "SystemConfiguration.h" #include "romfs.h" +#include "spifs.h" #include "NetTransport.h" #include "Helpers.h" #include "HTTPServer.h" @@ -102,8 +103,9 @@ esp_err_t WebGuiAppInit(void) } ESP_ERROR_CHECK(InitSysConfig()); - //init rom file system + //init file systems init_rom_fs("/espfs"); + init_spi_fs("/data"); #if CONFIG_WEBGUIAPP_GPRS_ENABLE diff --git a/src/spifs.c b/src/spifs.c new file mode 100644 index 0000000..0e114ca --- /dev/null +++ b/src/spifs.c @@ -0,0 +1,67 @@ + /*! Copyright 2022 Bogdan Pilyugin + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * \file spifs.c + * \version 1.0 + * \date 2022-11-13 + * \author Bogdan Pilyugin + * \brief + * \details + * \copyright Apache License, Version 2.0 + */ + +#include "spifs.h" +#include +#include +#include "esp_log.h" +#include "esp_err.h" +#include "sdkconfig.h" +#include "esp_spiffs.h" + + +static const char *TAG = "SPIFS"; + +esp_err_t init_spi_fs(const char *root) +{ + ESP_LOGI(TAG, "Initializing SPIFFS"); + + esp_vfs_spiffs_conf_t conf = { + .base_path = root, + .partition_label = NULL, + .max_files = 5, // This sets the maximum number of files that can be open at the same time + .format_if_mount_failed = true + }; + + esp_err_t ret = esp_vfs_spiffs_register(&conf); + if (ret != ESP_OK) { + if (ret == ESP_FAIL) { + ESP_LOGE(TAG, "Failed to mount or format filesystem"); + } else if (ret == ESP_ERR_NOT_FOUND) { + ESP_LOGE(TAG, "Failed to find SPIFFS partition"); + } else { + ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret)); + } + return ret; + } + + size_t total = 0, used = 0; + ret = esp_spiffs_info(NULL, &total, &used); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used); + return ESP_OK; +} diff --git a/upload_script.html b/upload_script.html new file mode 100644 index 0000000..c3bc094 --- /dev/null +++ b/upload_script.html @@ -0,0 +1,80 @@ + + + +
+

ESP32 File Server

+
+ + + + + + + + + + +
+ + + +
+ + + + + +
+
+