jblanked 1 год назад
Родитель
Сommit
b386ab6d15

+ 21 - 4
CHANGELOG.md

@@ -1,9 +1,26 @@
+## 0.3 (New Features)
+- Updated the progress messages displayed after sending a GET request.
+- Renamed the "Config" main submenu option to "Settings."
+- Added a submenu (WiFi, File, Request) that appears when users click the "Settings" option.
+
+Clicking **WiFi** displays a variable item list for SSID and Password. If you click:
+- **SSID**: A text input screen will appear with the current saved SSID pre-filled. You can change the SSID by editing the text and clicking "Save."
+- **Password**: A text input screen will appear with the current saved password pre-filled. You can change the password by editing the text and clicking "Save."
+
+Clicking **File** displays a variable item list with options to Read File, Set File Type, Rename File, and Delete File. If you click:
+- **Read File**: The contents of the saved file will be displayed.
+- **Set File Type**: A text input screen will appear with the current saved file type pre-filled. You can change the file type by editing the text and clicking "Save."
+- **Rename File**: A text input screen will appear with the current saved file name pre-filled. You can change the file name by editing the text and clicking "Save."
+- **Delete File**: The saved file will be deleted.
+
+Clicking **Request** displays a variable item list with a single text input option called **Path**. If you click **Path**, you can change the URL path that the GET request will be sent to.
+
 ## 0.2 (Stability Patch)
 - Changed serial functions from synchronous to asynchronous.
 - Added error handling for GET requests and WiFi Dev Board connections.
-- Updated the WiFi Dev Board firmware (FlipperHTTP) to trigger three quick green LED blinks when the board is first connected, a solid green LED when the board receives a serial command, and a solid green LED while the board is processing.
-- Added auto-connect and auto-disconnect for WiFi when a user enters or leaves the app.
-- Added an option to see the saved data
+- Updated the WiFi Dev Board firmware (FlipperHTTP) to blink the green LED three times when the board is first connected, remain solid green when the board receives a serial command, and stay solid green while processing.
+- Added auto-connect and auto-disconnect for WiFi when a user enters or exits the app.
+- Added an option to view saved data.
 
 ## 0.1
-- Initial release
+- Initial release

+ 31 - 42
README.md

@@ -1,79 +1,68 @@
 ## Overview
 
-**Web Crawler** is a custom application designed for the Flipper Zero device. This app allows users to configure and manage web crawling operations directly from their Flipper Zero.
+**Web Crawler** is a custom application designed for the Flipper Zero device, allowing users to configure and manage GET requests directly from their Flipper Zero.
 
 ## Requirements
-- WiFi Devboard for Flipper Zero with WebCrawler Flash: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
-- Wifi Access Point
+- WiFi Dev Board for Flipper Zero with WebCrawler Flash: https://github.com/jblanked/WebCrawler-FlipperZero/tree/main/assets/FlipperHTTP
+- WiFi Access Point
 
 ## Features
 
-- **Configurable Website Path**: Specify the URL of the website you want to crawl.
+- **Configurable Request**: Specify the URL of the website you want to send a GET request to.
 - **Wi-Fi Configuration**: Enter your Wi-Fi SSID and password to enable network communication.
-- **Data Storage**: Automatically saves received data to the device's storage for easy access.
-
+- **File Management**: Automatically saves and manages received data on the device's storage, allowing users to view, rename, and delete the received data at any time.
 
 ## Usage
 
-1. **Launch the Web Crawler App**
-
-   Navigate to the Apps menu on your Flipper Zero, select GPIO, then scroll down and select **Web Crawler**.
+1. **Connection**: Turn off your Flipper, connect the WiFi Dev Board, then turn your Flipper back on.
 
-2. **Main Menu**
+2. **Launch the Web Crawler App**: Navigate to the Apps menu on your Flipper Zero, select GPIO, then scroll down and select **Web Crawler**.
 
-   Upon launching, you'll be presented with a submenu containing the following options:
-
-   - **Run**: Initiate the web crawling operation.
+3. **Main Menu**: Upon launching, you'll see a submenu containing the following options:
+   - **Run**: Initiate the GET request.
    - **About**: View information about the Web Crawler app.
-   - **Configure**: Set up the crawling parameters.
-
-3. **Configuring Settings**
-
-   Select **Configure** to enter the following parameters:
-
-   - **Path**: Enter the URL of the website you wish to crawl.
-   - **SSID**: Input your Wi-Fi network's SSID.
-   - **Password**: Provide the corresponding Wi-Fi password.
-
-   Use the Flipper Zero's navigation buttons to input and confirm your settings. Once configured, these settings will be saved and used for subsequent crawling operations.
+   - **Settings**: Set up parameters or perform file operations.
 
-4. **Running the Crawler**
+4. **Settings**: Select **Settings** and navigate to WiFi Settings. Use the Flipper Zero's navigation buttons to input and confirm your settings. Do the same for the Request settings. Once configured, these settings will be saved and used for subsequent GET request operations.
 
-   Select **Run** from the main submenu to start the web crawling process. The app will:
-
-   - **Send Settings**: Transmit the configured path, SSID, and password via UART to the Wifi Dev Board.
-   - **Receive Data**: Listen for incoming data from the UART interface.
+5. **Running the Crawler**: Select **Run** from the main submenu to start the GET request process. The app will:
+   - **Send Settings**: Transmit the GET request via serial to the WiFi Dev Board.
+   - **Receive Data**: Listen for incoming data.
    - **Store Data**: Save the received data to the device's storage for later retrieval.
+   - **Log**: Display detailed analysis of the operation status on the screen.
 
-   Monitor the operation state displayed on the main screen to track progress.
-
-5. **Accessing Crawled Data**
-
-   After the crawling operation completes, navigate to the storage directory to access the received_data.txt file, which contains the crawled information.
+6. **Accessing Received Data**: After the GET request operation completes, you can access the received data by:
+   - Connecting to Flipper and opening the SD/apps_data/web_crawler_app/ storage directory to access the received_data.txt file (or the file name/type customized in the settings).
+   - Navigating to File Settings and selecting Read File.
 
 ## Setting Up Parameters
 
 1. **Path (URL)**
    - Enter the complete URL of the website you intend to crawl (e.g., https://www.example.com/).
 
-2. **SSID (Wi-Fi Network)**
-   - Provide the name of your Wi-Fi network to enable the Flipper Zero to communicate over the network if required.
+2. **SSID (WiFi Network)**
+   - Provide the name of your WiFi network to enable the Flipper Zero to communicate over the network.
 
-3. **Password (Wi-Fi Network)**
-   - Input the corresponding password for your Wi-Fi network.
+3. **Password (WiFi Network)**
+   - Input the corresponding password for your WiFi network.
+
+4. **Set File Type**
+   - Enter your desired file extension. After saving, the app will rename your file, applying the new extension.
+
+5. **Rename File**
+   - Provide your desired file name. After saving, the app will rename your file with the new name.
 
 ## Saving Settings
 
-After entering the desired configuration parameters, the app automatically saves these settings and uses them during the web crawling process. You can update these settings at any time by navigating back to the **Configure** menu.
+After entering the desired configuration parameters, the app automatically saves these settings for use during the GET request process. You can update these settings at any time by navigating back to the **Settings** menu.
 
 ## Logging and Debugging
 
-The Web Crawler app utilizes logging to help identify issues:
+The Web Crawler app uses logging to help identify issues:
 
 - **Info Logs**: Provide general information about the app's operations (e.g., UART initialization, sending settings).
 - **Error Logs**: Indicate problems encountered during execution (e.g., failed to open settings file).
 
 Connect your Flipper Zero to a computer and use a serial terminal to view these logs for detailed troubleshooting.
 
-
-*Happy Crawling! 🕷️*
+*Happy Crawling! 🕷️* 

+ 1 - 1
app.c

@@ -1,9 +1,9 @@
 #include <web_crawler_e.h>
 #include <flipper_http.h>
+#include <web_crawler_storage.h>
 #include <web_crawler_free.h>
 #include <web_crawler_callback.h>
 #include <web_crawler_i.h>
-#include <web_crawler_storage.h>
 /**
  * @brief      Entry point for the WebCrawler application.
  * @param      p  Input parameter - unused

+ 1 - 1
application.fam

@@ -8,7 +8,7 @@ App(
         "gui", 
     ],
     order=10,
-    fap_version = (0, 2),
+    fap_version = (0, 3),
     fap_icon="app.png",
     fap_category="GPIO",
     fap_author="JBlanked",

BIN
assets/.DS_Store


+ 29 - 5
assets/FlipperHTTP/README.md

@@ -24,16 +24,15 @@ HTTP library for Flipper Zero. Compatible with Wifi Dev Board for Flipper Zero (
 You should be all set. Here's the initial guide: https://www.youtube.com/watch?v=Y2lUVTMTABE
 
 
-# Usage
-The `flipper_http.h` file is for seamless use of the FlipperHTTP Firmware in `C` applications and is the root of the `Web Crawler` app. A Javascript library will be available soon, followed by a mPython library.
+## Usage in `C` (flipper_http.h)
 
-## General
+**General**
 - Init:
     - `flipper_http_init(FlipperHTTP_Callback callback, void *context)`
 - DeInit:
     - `flipper_http_deinit()`
 
-## Wifi
+**WiFi**
 - Connect To Wifi: 
     - `flipper_http_connect_wifi()`
 - Disconnect: 
@@ -43,7 +42,7 @@ The `flipper_http.h` file is for seamless use of the FlipperHTTP Firmware in `C`
 - Save Wifi: 
     - `flipper_http_save_wifi(const char *ssid, const char *password)`
 
-## Extras
+**Extras**
 - Send Data:
     - `flipper_http_send_data(const char *data)`
 - Rx Callback:
@@ -52,3 +51,28 @@ The `flipper_http.h` file is for seamless use of the FlipperHTTP Firmware in `C`
     - `flipper_http_get_request(const char *url)`
 - Save Data to SD:
     - `flipper_http_save_received_data(size_t bytes_received, const char line_buffer[])`
+
+## Usage in `JavaScript` (flipper_http.js):
+**General**
+- Init:
+    - `fhttp.init()`
+- DeInit:
+    - `fhttp.deinit()`
+
+**WiFi**
+- Connect To Wifi: 
+    - `fhttp.connect_wifi()`
+- Disconnect: 
+    - `fhttp.disconnect_wifi()`
+- Ping: 
+    - `fhttp.ping()`
+- Save Wifi: 
+    - `fhttp.save_wifi(ssid, password)`
+
+**Extras**
+- Send Data:
+    - `fhttp.send_data(data)`
+- Read Data:
+    - `fhttp.read_data(delay_ms)`
+- GET request:
+    - `fhttp.get_request(url)`

+ 139 - 35
assets/FlipperHTTP/flipper_http.h

@@ -10,28 +10,39 @@
 
 // STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
 
-#define HTTP_TAG "WebCrawler"
-#define http_tag "web_crawler_app"
-#define UART_CH (FuriHalSerialIdUsart)
+#define HTTP_TAG "WebCrawler"             // change this to your app name
+#define http_tag "web_crawler_app"        // change this to your app id
+#define UART_CH (FuriHalSerialIdUsart)    // UART channel
 #define TIMEOUT_DURATION_TICKS (2 * 1000) // 2 seconds
-#define BAUDRATE (115200)
-#define RX_BUF_SIZE 1024
+#define BAUDRATE (115200)                 // UART baudrate
+#define RX_BUF_SIZE 1024                  // UART RX buffer size
 
-// UART RX Handler Callback declaration
-void flipper_http_rx_callback(const char *line, void *context);
+// Forward declaration for callback
+typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
 
-// Function to save received data to a file
+// Functions
+bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
+void flipper_http_deinit();
+//---
+void flipper_http_rx_callback(const char *line, void *context);
+bool flipper_http_send_data(const char *data);
+//---
+bool flipper_http_connect_wifi();
+bool flipper_http_disconnect_wifi();
+bool flipper_http_ping();
+bool flipper_http_save_wifi(const char *ssid, const char *password);
+//---
+bool flipper_http_get_request(const char *url);
+//---
 bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[]);
 
-static bool flipper_http_connect_wifi();
-
 // Define GPIO pins for UART
 GpioPin test_pins[2] = {
     {.port = GPIOA, .pin = LL_GPIO_PIN_7}, // USART1_RX
     {.port = GPIOA, .pin = LL_GPIO_PIN_6}  // USART1_TX
 };
 
-// State variable to track if serial is receiving, sending, or idle
+// State variable to track the UART state
 typedef enum
 {
     INACTIVE,  // Inactive state
@@ -48,20 +59,17 @@ typedef enum
     WorkerEvtRxDone = (1 << 1),
 } WorkerEvtFlags;
 
-// Forward declaration for callback
-typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
-
 // FlipperHTTP Structure
 typedef struct
 {
-    FuriStreamBuffer *flipper_http_stream; // Stream buffer for UART communication
-    FuriHalSerialHandle *serial_handle;    // Serial handle for UART communication
-    FuriThread *rx_thread;
-    uint8_t rx_buf[RX_BUF_SIZE];
-    FuriThreadId rx_thread_id;
+    FuriStreamBuffer *flipper_http_stream;  // Stream buffer for UART communication
+    FuriHalSerialHandle *serial_handle;     // Serial handle for UART communication
+    FuriThread *rx_thread;                  // Worker thread for UART
+    uint8_t rx_buf[RX_BUF_SIZE];            // Buffer for received data
+    FuriThreadId rx_thread_id;              // Worker thread ID
     FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines
     void *callback_context;                 // Context for the callback
-    SerialState state;
+    SerialState state;                      // State of the UART
 
     // variable to store the last received data from the UART
     char *last_response;
@@ -76,7 +84,19 @@ typedef struct
 // Declare uart as extern to prevent multiple definitions
 FlipperHTTP fhttp;
 
+typedef struct
+{
+    char *key;
+    char *value;
+} FlipperHTTPHeader;
+
 // Timer callback function
+/**
+ * @brief      Callback function for the GET timeout timer.
+ * @return     0
+ * @param      context   The context to pass to the callback.
+ * @note       This function will be called when the GET request times out.
+ */
 void get_timeout_timer_callback(void *context)
 {
     UNUSED(context);
@@ -84,7 +104,6 @@ void get_timeout_timer_callback(void *context)
 
     // Reset the state
     fhttp.started_receiving = false;
-    fhttp.just_started = false;
 
     // Free received data if any
     if (fhttp.received_data)
@@ -98,6 +117,14 @@ void get_timeout_timer_callback(void *context)
 }
 
 // UART RX Handler Callback (Interrupt Context)
+/**
+ * @brief      A private callback function to handle received data asynchronously.
+ * @return     void
+ * @param      handle    The UART handle.
+ * @param      event     The event type.
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
 static void _flipper_http_rx_callback(FuriHalSerialHandle *handle, FuriHalSerialRxEvent event, void *context)
 {
     UNUSED(context);
@@ -110,6 +137,12 @@ static void _flipper_http_rx_callback(FuriHalSerialHandle *handle, FuriHalSerial
 }
 
 // UART worker thread
+/**
+ * @brief      Worker thread to handle UART data asynchronously.
+ * @return     0
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
 static int32_t flipper_http_worker(void *context)
 {
     UNUSED(context);
@@ -150,6 +183,13 @@ static int32_t flipper_http_worker(void *context)
 }
 
 // UART initialization function
+/**
+ * @brief      Initialize UART.
+ * @return     true if the UART was initialized successfully, false otherwise.
+ * @param      callback  The callback function to handle received data (ex. flipper_http_rx_callback).
+ * @param      context   The context to pass to the callback.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
 bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
 {
     fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
@@ -236,6 +276,11 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
 }
 
 // Deinitialize UART
+/**
+ * @brief      Deinitialize UART.
+ * @return     void
+ * @note       This function will stop the asynchronous RX, release the serial handle, and free the resources.
+ */
 void flipper_http_deinit()
 {
     if (fhttp.serial_handle == NULL)
@@ -279,6 +324,12 @@ void flipper_http_deinit()
 }
 
 // Function to send data over UART with newline termination
+/**
+ * @brief      Send data over UART with newline termination.
+ * @return     true if the data was sent successfully, false otherwise.
+ * @param      data  The data to send over UART.
+ * @note       The data will be sent over UART with a newline character appended.
+ */
 bool flipper_http_send_data(const char *data)
 {
     size_t data_length = strlen(data);
@@ -311,11 +362,21 @@ bool flipper_http_send_data(const char *data)
     fhttp.state = SENDING;
     furi_hal_serial_tx(fhttp.serial_handle, (const uint8_t *)send_buffer, send_length);
 
-    FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer);
+    // Uncomment below line to log the data sent over UART
+    // FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer);
     fhttp.state = IDLE;
     return true;
 }
 
+// Function to send a PING request
+/**
+ * @brief      Send a GET request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the GET request to.
+ * @note       The received data will be handled asynchronously via the callback.
+ * @note       This is best used to check if the Wifi Dev Board is connected.
+ * @note       The state will remain INACTIVE until a PONG is received.
+ */
 bool flipper_http_ping()
 {
     const char *command = "[PING]";
@@ -331,8 +392,18 @@ bool flipper_http_ping()
 }
 
 // Function to save WiFi settings (returns true if successful)
+/**
+ * @brief      Send a command to save WiFi settings.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
 bool flipper_http_save_wifi(const char *ssid, const char *password)
 {
+    if (!ssid || !password)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_save_wifi.");
+        return false;
+    }
     char buffer[256];
     int ret = snprintf(buffer, sizeof(buffer), "[WIFI/SAVE]{\"ssid\":\"%s\",\"password\":\"%s\"}", ssid, password);
     if (ret < 0 || ret >= (int)sizeof(buffer))
@@ -352,6 +423,11 @@ bool flipper_http_save_wifi(const char *ssid, const char *password)
 }
 
 // Function to disconnect from WiFi (returns true if successful)
+/**
+ * @brief      Send a command to disconnect from WiFi.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
 bool flipper_http_disconnect_wifi()
 {
     const char *command = "[WIFI/DISCONNECT]";
@@ -366,7 +442,12 @@ bool flipper_http_disconnect_wifi()
 }
 
 // Function to connect to WiFi (returns true if successful)
-static bool flipper_http_connect_wifi()
+/**
+ * @brief      Send a command to connect to WiFi.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
+bool flipper_http_connect_wifi()
 {
     const char *command = "[WIFI/CONNECT]";
     if (!flipper_http_send_data(command))
@@ -414,7 +495,14 @@ bool flipper_http_get_request(const char *url)
     return true;
 }
 
-// UART RX Handler Callback
+// Function to handle received data asynchronously
+/**
+ * @brief      Callback function to handle received data asynchronously.
+ * @return     void
+ * @param      line     The received line.
+ * @param      context  The context passed to the callback.
+ * @note       The received data will be handled asynchronously via the callback and handles the state of the UART.
+ */
 void flipper_http_rx_callback(const char *line, void *context)
 {
 
@@ -432,8 +520,8 @@ void flipper_http_rx_callback(const char *line, void *context)
         fhttp.state = RECEIVING;
     }
 
-    // Process the received line
-    FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
+    // Uncomment below line to log the data received over UART
+    // FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
 
     // Check if we've started receiving data from a GET request
     if (fhttp.started_receiving)
@@ -452,15 +540,19 @@ void flipper_http_rx_callback(const char *line, void *context)
                 flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
                 free(fhttp.received_data);
                 fhttp.received_data = NULL;
+                fhttp.started_receiving = false;
+                fhttp.just_started = false;
+                fhttp.state = IDLE;
+                return;
             }
             else
             {
                 FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving = false;
+                fhttp.just_started = false;
+                fhttp.state = IDLE;
+                return;
             }
-            fhttp.started_receiving = false;
-            fhttp.just_started = false;
-            fhttp.state = IDLE;
-            return;
         }
 
         // Append the new line to the existing data
@@ -470,17 +562,20 @@ void flipper_http_rx_callback(const char *line, void *context)
             if (fhttp.received_data)
             {
                 strcpy(fhttp.received_data, line);
-                strcat(fhttp.received_data, "\n");
+                fhttp.received_data[strlen(line)] = '\n';     // Add newline
+                fhttp.received_data[strlen(line) + 1] = '\0'; // Null terminator
             }
         }
         else
         {
-            size_t new_size = strlen(fhttp.received_data) + strlen(line) + 2; // +2 for newline and null terminator
+            size_t current_len = strlen(fhttp.received_data);
+            size_t new_size = current_len + strlen(line) + 2; // +2 for newline and null terminator
             fhttp.received_data = (char *)realloc(fhttp.received_data, new_size);
             if (fhttp.received_data)
             {
-                strcat(fhttp.received_data, line);
-                strcat(fhttp.received_data, "\n");
+                memcpy(fhttp.received_data + current_len, line, strlen(line)); // Copy line at the end of the current data
+                fhttp.received_data[current_len + strlen(line)] = '\n';        // Add newline
+                fhttp.received_data[current_len + strlen(line) + 1] = '\0';    // Null terminator
             }
         }
 
@@ -510,6 +605,8 @@ void flipper_http_rx_callback(const char *line, void *context)
         FURI_LOG_I(HTTP_TAG, "GET request succeeded.");
         fhttp.started_receiving = true;
         furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        fhttp.state = RECEIVING;
+        return;
     }
     else if (strstr(line, "[DISCONNECTED]") != NULL)
     {
@@ -546,7 +643,14 @@ void flipper_http_rx_callback(const char *line, void *context)
         fhttp.state = IDLE;
     }
 }
-
+// Function to save received data to a file
+/**
+ * @brief      Save the received data to a file.
+ * @return     true if the data was saved successfully, false otherwise.
+ * @param      bytes_received  The number of bytes received.
+ * @param      line_buffer     The buffer containing the received data.
+ * @note       The data will be saved to a file in the STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt" directory.
+ */
 bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[])
 {
     const char *output_file_path = STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt";

+ 225 - 0
assets/FlipperHTTP/flipper_http.js

@@ -0,0 +1,225 @@
+// Description: Flipper HTTP API for the Flipper Wifi Developer Board.
+// Global: flipper_http_init, flipper_http_deinit, flipper_http_rx_callback(), flipper_http_send_data, flipper_http_connect_wifi, flipper_http_disconnect_wifi, flipper_http_ping, flipper_http_save_wifi, flipper_http_get_request
+// License: MIT
+// Author: JBlanked
+// File: flipper_http.js
+
+let serial = require("serial");
+
+// Define the global `fhttp` object with all the functions
+let fhttp = {
+    // Constructor
+    init: function () {
+        serial.setup("usart", 115200);
+    },
+    // Deconstructor
+    deinit: function () {
+        serial.end();
+    },
+    // Read data from the serial port and return it line by line
+    read_data: function (delay_ms) {
+        let line = serial.readln(delay_ms);
+        let i = 5;
+        while (line === undefined && i > 0) {
+            line = serial.readln(delay_ms);
+            i--;
+        }
+        return line;
+    },
+    // Send data to the serial port
+    send_data: function (data) {
+        if (data === "") {
+            return;
+        }
+        serial.write(data);
+    },
+    // Clear the incoming serial by up to 10 lines
+    clear_buffer: function (search_for_success) {
+        let data = this.read_data(100);
+        let sdata = this.to_string(data);
+        let i = 0;
+        // clear all data until we get an expected response
+        while (i < 5 &&
+            (data !== undefined &&
+                (!search_for_success || (search_for_success && !this.includes(sdata, "[SUCCESS]"))) &&
+                !this.includes(sdata, "[ERROR]") &&
+                !this.includes(sdata, "[INFO]") &&
+                !this.includes(sdata, "[PONG]") &&
+                !this.includes(sdata, "[DISCONNECTED]") &&
+                !this.includes(sdata, "[CONNECTED]") &&
+                !this.includes(sdata, "[GET/STARTED]") &&
+                !this.includes(sdata, "[GET/END]"))) {
+            data = this.read_data(100);
+            sdata = this.to_string(data);
+            i++;
+        }
+    },
+    // Connect to wifi
+    connect_wifi: function () {
+        serial.write("[WIFI/CONNECT]");
+        let response = this.read_data(500);
+        if (response === undefined) {
+            return false;
+        }
+        this.clear_buffer(true); // Clear the buffer
+        return this.includes(this.to_string(response), "[SUCCESS]") || this.includes(this.to_string(response), "[CONNECTED]") || this.includes(this.to_string(response), "[INFO]");
+    },
+    // Disconnect from wifi
+    disconnect_wifi: function () {
+        serial.write("[WIFI/DISCONNECT]");
+        let response = this.read_data(500);
+        if (response === undefined) {
+            return false;
+        }
+        this.clear_buffer(true); // Clear the buffer
+        return this.includes(this.to_string(response), "[DISCONNECTED]") || this.includes(this.to_string(response), "WiFi stop");
+    },
+    // Send a ping to the board
+    ping: function () {
+        serial.write("[PING]");
+        let response = this.read_data(100);
+        if (response === undefined) {
+            return false;
+        }
+        this.clear_buffer(true); // Clear the buffer
+        return this.includes(this.to_string(response), "[PONG]");
+    },
+    // Save wifi settings
+    save_wifi: function (ssid, password) {
+        if (ssid === "" || password === "") {
+            return false;
+        }
+        let command = '[WIFI/SAVE]{"ssid":"' + ssid + '","password":"' + password + '"}';
+        serial.write(command);
+        let response = this.read_data(500);
+        if (response === undefined) {
+            this.clear_buffer(false); // Clear the buffer
+            return false;
+        }
+        let sresponse = this.to_string(response);
+        if (this.includes(sresponse, "[SUCCESS]")) {
+            this.clear_buffer(false); // Clear the buffer
+            this.clear_buffer(false); // Clear the buffer
+            return true;
+        }
+        else {
+            print("Failed to save: " + response);
+            this.clear_buffer(false); // Clear the buffer
+            return false;
+        }
+    },
+    // Send a GET request to the board
+    // I reduced this to return the first line of the response that isnt undefined
+    // You'll also get 'out of memory' errors if you try to read/return too much data
+    // As mjs is updated, this can be improved
+    get_request: function (url) {
+        serial.write('[GET]' + url);
+        if (this.read_data(500) === "[GET/SUCCESS] GET request successful.") {
+            print("GET request successful");
+            while (true) {
+                let line = this.read_data(500);
+                if (line === "[GET/END]") {
+                    break;
+                }
+                if (line !== undefined) {
+                    this.clear_buffer(false); // Clear the buffer
+                    return line;
+                }
+            }
+        }
+        else {
+            print("GET request failed");
+        }
+        this.clear_buffer(); // Clear the buffer
+        return "";
+    },
+    // Helper function to check if a string contains another string
+    includes: function (text, search) {
+        let stringLength = text.length;
+        let searchLength = search.length;
+        if (stringLength < searchLength) {
+            return false;
+        }
+        for (let i = 0; i < stringLength; i++) {
+            if (text[i] === search[0]) {
+                let found = true;
+                for (let j = 1; j < searchLength; j++) {
+                    if (text[i + j] !== search[j]) {
+                        found = false;
+                        break;
+                    }
+                }
+                if (found) {
+                    return true;
+                }
+            }
+        }
+    },
+    // Convert an array of characters to a string
+    to_string: function (text) {
+        if (text === undefined) {
+            return "";
+        }
+        let return_text = "";
+        for (let i = 0; i < text.length; i++) {
+            return_text += text[i];
+        }
+        return return_text;
+    }
+};
+
+/* Example Usage:
+
+let textbox = require("textbox");
+textbox.setConfig("end", "text");
+textbox.show();
+textbox.addText("Flipper HTTP Example:\n\n");
+// Initialize the flipper http object
+fhttp.init();
+textbox.addText("Initialized!\n");
+// Send ping to the board
+let response = fhttp.ping();
+
+if (response) {
+    textbox.addText("Ping successful\nSaving wifi settings...\n");
+    let success = fhttp.save_wifi("JBlanked", "maingirl");
+    if (success) {
+        textbox.addText("Wifi settings saved\nSending GET request..\n");
+        let url = "https://catfact.ninja/fact";
+        let data = fhttp.get_request(url, 0);
+        if (data !== undefined && data !== "") {
+            textbox.addText("GET request successful!\n\nReturned Data: \n\n" + data + "\n\nDisconnecting from wifi...\n");
+            if (fhttp.disconnect_wifi()) {
+                textbox.addText("Disconnected from wifi.\n");
+            }
+            else {
+                textbox.addText("Failed to disconnect from wifi.\n");
+            }
+
+        }
+        else {
+            textbox.addText("GET request failed.\nDisconnecting from wifi...\n");
+            if (fhttp.disconnect_wifi()) {
+                textbox.addText("Disconnected from wifi.\n");
+            }
+            else {
+                textbox.addText("Failed to disconnect from wifi.\n");
+            }
+        }
+    }
+    else {
+        textbox.addText("Wifi settings failed to save.\n");
+    }
+}
+else {
+    textbox.addText("Ping failed.\n");
+}
+textbox.addText("Press BACK twice to exit..\n");
+delay(100000); // Wait for user to hit back
+textbox.addText("\nTimeout exceeded.\nExiting...\n");
+delay(5000);
+// Destructor
+fhttp.deinit();
+
+textbox.addText("Deinitialized!\n");
+*/

BIN
assets/FlipperHTTP/flipper_http_firmware_a.bin


+ 131 - 28
flipper_http.h

@@ -10,17 +10,30 @@
 
 // STORAGE_EXT_PATH_PREFIX is defined in the Furi SDK as /ext
 
-#define HTTP_TAG "WebCrawler"
-#define http_tag "web_crawler_app"
-#define UART_CH (FuriHalSerialIdUsart)
+#define HTTP_TAG "WebCrawler"             // change this to your app name
+#define http_tag "web_crawler_app"        // change this to your app id
+#define UART_CH (FuriHalSerialIdUsart)    // UART channel
 #define TIMEOUT_DURATION_TICKS (2 * 1000) // 2 seconds
-#define BAUDRATE (115200)
-#define RX_BUF_SIZE 1024
+#define BAUDRATE (115200)                 // UART baudrate
+#define RX_BUF_SIZE 1024                  // UART RX buffer size
 
-// UART RX Handler Callback declaration
-void flipper_http_rx_callback(const char *line, void *context);
+// Forward declaration for callback
+typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
 
-// Function to save received data to a file
+// Functions
+bool flipper_http_init(FlipperHTTP_Callback callback, void *context);
+void flipper_http_deinit();
+//---
+void flipper_http_rx_callback(const char *line, void *context);
+bool flipper_http_send_data(const char *data);
+//---
+bool flipper_http_connect_wifi();
+bool flipper_http_disconnect_wifi();
+bool flipper_http_ping();
+bool flipper_http_save_wifi(const char *ssid, const char *password);
+//---
+bool flipper_http_get_request(const char *url);
+//---
 bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[]);
 
 // Define GPIO pins for UART
@@ -29,7 +42,7 @@ GpioPin test_pins[2] = {
     {.port = GPIOA, .pin = LL_GPIO_PIN_6}  // USART1_TX
 };
 
-// State variable to track if serial is receiving, sending, or idle
+// State variable to track the UART state
 typedef enum
 {
     INACTIVE,  // Inactive state
@@ -46,20 +59,17 @@ typedef enum
     WorkerEvtRxDone = (1 << 1),
 } WorkerEvtFlags;
 
-// Forward declaration for callback
-typedef void (*FlipperHTTP_Callback)(const char *line, void *context);
-
 // FlipperHTTP Structure
 typedef struct
 {
-    FuriStreamBuffer *flipper_http_stream; // Stream buffer for UART communication
-    FuriHalSerialHandle *serial_handle;    // Serial handle for UART communication
-    FuriThread *rx_thread;
-    uint8_t rx_buf[RX_BUF_SIZE];
-    FuriThreadId rx_thread_id;
+    FuriStreamBuffer *flipper_http_stream;  // Stream buffer for UART communication
+    FuriHalSerialHandle *serial_handle;     // Serial handle for UART communication
+    FuriThread *rx_thread;                  // Worker thread for UART
+    uint8_t rx_buf[RX_BUF_SIZE];            // Buffer for received data
+    FuriThreadId rx_thread_id;              // Worker thread ID
     FlipperHTTP_Callback handle_rx_line_cb; // Callback for received lines
     void *callback_context;                 // Context for the callback
-    SerialState state;
+    SerialState state;                      // State of the UART
 
     // variable to store the last received data from the UART
     char *last_response;
@@ -74,7 +84,19 @@ typedef struct
 // Declare uart as extern to prevent multiple definitions
 FlipperHTTP fhttp;
 
+typedef struct
+{
+    char *key;
+    char *value;
+} FlipperHTTPHeader;
+
 // Timer callback function
+/**
+ * @brief      Callback function for the GET timeout timer.
+ * @return     0
+ * @param      context   The context to pass to the callback.
+ * @note       This function will be called when the GET request times out.
+ */
 void get_timeout_timer_callback(void *context)
 {
     UNUSED(context);
@@ -82,7 +104,6 @@ void get_timeout_timer_callback(void *context)
 
     // Reset the state
     fhttp.started_receiving = false;
-    fhttp.just_started = false;
 
     // Free received data if any
     if (fhttp.received_data)
@@ -96,6 +117,14 @@ void get_timeout_timer_callback(void *context)
 }
 
 // UART RX Handler Callback (Interrupt Context)
+/**
+ * @brief      A private callback function to handle received data asynchronously.
+ * @return     void
+ * @param      handle    The UART handle.
+ * @param      event     The event type.
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
 static void _flipper_http_rx_callback(FuriHalSerialHandle *handle, FuriHalSerialRxEvent event, void *context)
 {
     UNUSED(context);
@@ -108,6 +137,12 @@ static void _flipper_http_rx_callback(FuriHalSerialHandle *handle, FuriHalSerial
 }
 
 // UART worker thread
+/**
+ * @brief      Worker thread to handle UART data asynchronously.
+ * @return     0
+ * @param      context   The context to pass to the callback.
+ * @note       This function will handle received data asynchronously via the callback.
+ */
 static int32_t flipper_http_worker(void *context)
 {
     UNUSED(context);
@@ -148,6 +183,13 @@ static int32_t flipper_http_worker(void *context)
 }
 
 // UART initialization function
+/**
+ * @brief      Initialize UART.
+ * @return     true if the UART was initialized successfully, false otherwise.
+ * @param      callback  The callback function to handle received data (ex. flipper_http_rx_callback).
+ * @param      context   The context to pass to the callback.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
 bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
 {
     fhttp.flipper_http_stream = furi_stream_buffer_alloc(RX_BUF_SIZE, 1);
@@ -234,6 +276,11 @@ bool flipper_http_init(FlipperHTTP_Callback callback, void *context)
 }
 
 // Deinitialize UART
+/**
+ * @brief      Deinitialize UART.
+ * @return     void
+ * @note       This function will stop the asynchronous RX, release the serial handle, and free the resources.
+ */
 void flipper_http_deinit()
 {
     if (fhttp.serial_handle == NULL)
@@ -277,6 +324,12 @@ void flipper_http_deinit()
 }
 
 // Function to send data over UART with newline termination
+/**
+ * @brief      Send data over UART with newline termination.
+ * @return     true if the data was sent successfully, false otherwise.
+ * @param      data  The data to send over UART.
+ * @note       The data will be sent over UART with a newline character appended.
+ */
 bool flipper_http_send_data(const char *data)
 {
     size_t data_length = strlen(data);
@@ -309,11 +362,21 @@ bool flipper_http_send_data(const char *data)
     fhttp.state = SENDING;
     furi_hal_serial_tx(fhttp.serial_handle, (const uint8_t *)send_buffer, send_length);
 
-    FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer);
+    // Uncomment below line to log the data sent over UART
+    // FURI_LOG_I("FlipperHTTP", "Sent data over UART: %s", send_buffer);
     fhttp.state = IDLE;
     return true;
 }
 
+// Function to send a PING request
+/**
+ * @brief      Send a GET request to the specified URL.
+ * @return     true if the request was successful, false otherwise.
+ * @param      url  The URL to send the GET request to.
+ * @note       The received data will be handled asynchronously via the callback.
+ * @note       This is best used to check if the Wifi Dev Board is connected.
+ * @note       The state will remain INACTIVE until a PONG is received.
+ */
 bool flipper_http_ping()
 {
     const char *command = "[PING]";
@@ -329,8 +392,18 @@ bool flipper_http_ping()
 }
 
 // Function to save WiFi settings (returns true if successful)
+/**
+ * @brief      Send a command to save WiFi settings.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
 bool flipper_http_save_wifi(const char *ssid, const char *password)
 {
+    if (!ssid || !password)
+    {
+        FURI_LOG_E("FlipperHTTP", "Invalid arguments provided to flipper_http_save_wifi.");
+        return false;
+    }
     char buffer[256];
     int ret = snprintf(buffer, sizeof(buffer), "[WIFI/SAVE]{\"ssid\":\"%s\",\"password\":\"%s\"}", ssid, password);
     if (ret < 0 || ret >= (int)sizeof(buffer))
@@ -350,6 +423,11 @@ bool flipper_http_save_wifi(const char *ssid, const char *password)
 }
 
 // Function to disconnect from WiFi (returns true if successful)
+/**
+ * @brief      Send a command to disconnect from WiFi.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
 bool flipper_http_disconnect_wifi()
 {
     const char *command = "[WIFI/DISCONNECT]";
@@ -364,6 +442,11 @@ bool flipper_http_disconnect_wifi()
 }
 
 // Function to connect to WiFi (returns true if successful)
+/**
+ * @brief      Send a command to connect to WiFi.
+ * @return     true if the request was successful, false otherwise.
+ * @note       The received data will be handled asynchronously via the callback.
+ */
 bool flipper_http_connect_wifi()
 {
     const char *command = "[WIFI/CONNECT]";
@@ -412,7 +495,14 @@ bool flipper_http_get_request(const char *url)
     return true;
 }
 
-// UART RX Handler Callback
+// Function to handle received data asynchronously
+/**
+ * @brief      Callback function to handle received data asynchronously.
+ * @return     void
+ * @param      line     The received line.
+ * @param      context  The context passed to the callback.
+ * @note       The received data will be handled asynchronously via the callback and handles the state of the UART.
+ */
 void flipper_http_rx_callback(const char *line, void *context)
 {
 
@@ -430,8 +520,8 @@ void flipper_http_rx_callback(const char *line, void *context)
         fhttp.state = RECEIVING;
     }
 
-    // Process the received line
-    FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
+    // Uncomment below line to log the data received over UART
+    // FURI_LOG_I(HTTP_TAG, "Received UART line: %s", line);
 
     // Check if we've started receiving data from a GET request
     if (fhttp.started_receiving)
@@ -450,15 +540,19 @@ void flipper_http_rx_callback(const char *line, void *context)
                 flipper_http_save_received_data(strlen(fhttp.received_data), fhttp.received_data);
                 free(fhttp.received_data);
                 fhttp.received_data = NULL;
+                fhttp.started_receiving = false;
+                fhttp.just_started = false;
+                fhttp.state = IDLE;
+                return;
             }
             else
             {
                 FURI_LOG_E(HTTP_TAG, "No data received.");
+                fhttp.started_receiving = false;
+                fhttp.just_started = false;
+                fhttp.state = IDLE;
+                return;
             }
-            fhttp.started_receiving = false;
-            fhttp.just_started = false;
-            fhttp.state = IDLE;
-            return;
         }
 
         // Append the new line to the existing data
@@ -511,6 +605,8 @@ void flipper_http_rx_callback(const char *line, void *context)
         FURI_LOG_I(HTTP_TAG, "GET request succeeded.");
         fhttp.started_receiving = true;
         furi_timer_start(fhttp.get_timeout_timer, TIMEOUT_DURATION_TICKS);
+        fhttp.state = RECEIVING;
+        return;
     }
     else if (strstr(line, "[DISCONNECTED]") != NULL)
     {
@@ -547,7 +643,14 @@ void flipper_http_rx_callback(const char *line, void *context)
         fhttp.state = IDLE;
     }
 }
-
+// Function to save received data to a file
+/**
+ * @brief      Save the received data to a file.
+ * @return     true if the data was saved successfully, false otherwise.
+ * @param      bytes_received  The number of bytes received.
+ * @param      line_buffer     The buffer containing the received data.
+ * @note       The data will be saved to a file in the STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt" directory.
+ */
 bool flipper_http_save_received_data(size_t bytes_received, const char line_buffer[])
 {
     const char *output_file_path = STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt";

+ 435 - 70
web_crawler_callback.h

@@ -8,15 +8,22 @@ static WebCrawlerApp *app_instance = NULL;
 static void web_crawler_setting_item_path_clicked(void *context, uint32_t index);
 static void web_crawler_setting_item_ssid_clicked(void *context, uint32_t index);
 static void web_crawler_setting_item_password_clicked(void *context, uint32_t index);
+static void web_crawler_setting_item_file_type_clicked(void *context, uint32_t index);
+static void web_crawler_setting_item_file_rename_clicked(void *context, uint32_t index);
+static void web_crawler_setting_item_file_delete_clicked(void *context, uint32_t index);
+static void web_crawler_setting_item_file_read_clicked(void *context, uint32_t index);
 
 static void web_crawler_view_draw_callback(Canvas *canvas, void *context)
 {
     UNUSED(context);
-
-    WebCrawlerApp *app = app_instance;
-    if (!app)
+    if (!app_instance)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    if (!canvas)
     {
-        FURI_LOG_E(TAG, "App is NULL");
+        FURI_LOG_E(TAG, "Canvas is NULL");
         return;
     }
 
@@ -34,7 +41,7 @@ static void web_crawler_view_draw_callback(Canvas *canvas, void *context)
         return;
     }
 
-    if (app->path)
+    if (app_instance->path)
     {
         if (!sent_get_request)
         {
@@ -42,14 +49,15 @@ static void web_crawler_view_draw_callback(Canvas *canvas, void *context)
             canvas_draw_str(canvas, 0, 10, "Sending GET request...");
 
             // Perform GET request and handle the response
-            get_success = flipper_http_get_request(app->path);
+            get_success = flipper_http_get_request(app_instance->path);
 
             canvas_draw_str(canvas, 0, 20, "Sent!");
 
             if (get_success)
             {
                 canvas_draw_str(canvas, 0, 30, "Receiving data...");
-                get_success = true;
+                // Set the state to RECEIVING to ensure we continue to see the receiving message
+                fhttp.state = RECEIVING;
             }
             else
             {
@@ -60,14 +68,13 @@ static void web_crawler_view_draw_callback(Canvas *canvas, void *context)
         }
         else
         {
+            // print state
             if (get_success && fhttp.state == RECEIVING)
             {
                 canvas_draw_str(canvas, 0, 10, "Receiving and parsing data...");
-                already_success = true;
             }
             else if (get_success && fhttp.state == IDLE)
             {
-                already_success = true;
                 canvas_draw_str(canvas, 0, 10, "Data saved to file.");
                 canvas_draw_str(canvas, 0, 20, "Press BACK to return.");
             }
@@ -116,28 +123,53 @@ static void web_crawler_view_draw_callback(Canvas *canvas, void *context)
  * @param      context   The context - WebCrawlerApp object.
  * @return     WebCrawlerViewSubmenu
  */
+static uint32_t web_crawler_back_to_configure_callback(void *context)
+{
+    UNUSED(context);
+    // free file read widget if it exists
+    if (app_instance->widget_file_read)
+    {
+        widget_reset(app_instance->widget_file_read);
+    }
+    return WebCrawlerViewSubmenuConfig; // Return to the configure screen
+}
+
+/**
+ * @brief      Navigation callback to handle returning to the Wifi Settings screen.
+ * @param      context   The context - WebCrawlerApp object.
+ * @return     WebCrawlerViewSubmenu
+ */
 static uint32_t web_crawler_back_to_main_callback(void *context)
 {
     UNUSED(context);
+    // reset GET request flags
     sent_get_request = false;
     get_success = false;
     already_success = false;
-    if (app_instance && app_instance->textbox)
+    // free file read widget if it exists
+    if (app_instance->widget_file_read)
     {
-        widget_reset(app_instance->textbox);
+        widget_reset(app_instance->widget_file_read);
     }
-    return WebCrawlerViewSubmenu; // Return to the main submenu view
+    return WebCrawlerViewSubmenuMain; // Return to the main submenu
 }
 
-/**
- * @brief      Navigation callback to handle returning to the Configure screen.
- * @param      context   The context - WebCrawlerApp object.
- * @return     WebCrawlerViewConfigure
- */
-static uint32_t web_crawler_back_to_configure_callback(void *context)
+static uint32_t web_crawler_back_to_file_callback(void *context)
 {
     UNUSED(context);
-    return WebCrawlerViewConfigure; // Return to the Configure screen
+    return WebCrawlerViewVariableItemListFile; // Return to the file submenu
+}
+
+static uint32_t web_crawler_back_to_wifi_callback(void *context)
+{
+    UNUSED(context);
+    return WebCrawlerViewVariableItemListWifi; // Return to the wifi submenu
+}
+
+static uint32_t web_crawler_back_to_request_callback(void *context)
+{
+    UNUSED(context);
+    return WebCrawlerViewVariableItemListRequest; // Return to the request submenu
 }
 
 /**
@@ -159,37 +191,81 @@ static uint32_t web_crawler_exit_app_callback(void *context)
 static void web_crawler_submenu_callback(void *context, uint32_t index)
 {
     WebCrawlerApp *app = (WebCrawlerApp *)context;
-    furi_check(app);
+
+    if (app->view_dispatcher)
+    {
+        switch (index)
+        {
+        case WebCrawlerSubmenuIndexRun:
+            sent_get_request = false; // Reset the flag
+            view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewRun);
+            break;
+        case WebCrawlerSubmenuIndexAbout:
+            view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewAbout);
+            break;
+        case WebCrawlerSubmenuIndexConfig:
+            view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewSubmenuConfig);
+            break;
+        case WebCrawlerSubmenuIndexWifi:
+            view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListWifi);
+            break;
+        case WebCrawlerSubmenuIndexRequest:
+            view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListRequest);
+            break;
+        case WebCrawlerSubmenuIndexFile:
+            view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListFile);
+            break;
+        default:
+            FURI_LOG_E(TAG, "Unknown submenu index");
+            break;
+        }
+    }
+}
+
+/**
+ * @brief      Configuration enter callback to handle different items.
+ * @param      context   The context - WebCrawlerApp object.
+ * @param      index     The index of the item that was clicked.
+ */
+static void web_crawler_wifi_enter_callback(void *context, uint32_t index)
+{
     switch (index)
     {
-    case WebCrawlerSubmenuIndexRun:
-        sent_get_request = false; // Reset the flag
-        view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewRun);
+    case 0: // SSID
+        web_crawler_setting_item_ssid_clicked(context, index);
         break;
-    case WebCrawlerSubmenuIndexAbout:
-        view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewAbout);
+    case 1: // Password
+        web_crawler_setting_item_password_clicked(context, index);
         break;
-    case WebCrawlerSubmenuIndexSetPath:
-        view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewConfigure);
+    default:
+        FURI_LOG_E(TAG, "Unknown configuration item index");
         break;
-    case WebCrawlerSubmenuIndexData:
-        if (!load_received_data())
-        {
-            if (app_instance->textbox)
-            {
-                widget_reset(app_instance->textbox);
-                widget_add_text_scroll_element(
-                    app_instance->textbox,
-                    0,
-                    0,
-                    128,
-                    64, "File is empty.");
-            }
-        }
-        view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewData);
+    }
+}
+
+/**
+ * @brief      Configuration enter callback to handle different items.
+ * @param      context   The context - WebCrawlerApp object.
+ * @param      index     The index of the item that was clicked.
+ */
+static void web_crawler_file_enter_callback(void *context, uint32_t index)
+{
+    switch (index)
+    {
+    case 0: // File Read
+        web_crawler_setting_item_file_read_clicked(context, index);
+        break;
+    case 1: // FIle Type
+        web_crawler_setting_item_file_type_clicked(context, index);
+        break;
+    case 2: // File Rename
+        web_crawler_setting_item_file_rename_clicked(context, index);
+        break;
+    case 3: // File Delete
+        web_crawler_setting_item_file_delete_clicked(context, index);
         break;
     default:
-        FURI_LOG_E(TAG, "Unknown submenu index");
+        FURI_LOG_E(TAG, "Unknown configuration item index");
         break;
     }
 }
@@ -199,19 +275,13 @@ static void web_crawler_submenu_callback(void *context, uint32_t index)
  * @param      context   The context - WebCrawlerApp object.
  * @param      index     The index of the item that was clicked.
  */
-static void web_crawler_config_enter_callback(void *context, uint32_t index)
+static void web_crawler_request_enter_callback(void *context, uint32_t index)
 {
     switch (index)
     {
-    case 0:
+    case 0: // URL
         web_crawler_setting_item_path_clicked(context, index);
         break;
-    case 1:
-        web_crawler_setting_item_ssid_clicked(context, index);
-        break;
-    case 2:
-        web_crawler_setting_item_password_clicked(context, index);
-        break;
     default:
         FURI_LOG_E(TAG, "Unknown configuration item index");
         break;
@@ -225,9 +295,16 @@ static void web_crawler_config_enter_callback(void *context, uint32_t index)
 static void web_crawler_set_path_updated(void *context)
 {
     WebCrawlerApp *app = (WebCrawlerApp *)context;
-
-    furi_check(app);
-
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    if (!app->path || !app->temp_buffer_path || !app->temp_buffer_size_path || !app->path_item)
+    {
+        FURI_LOG_E(TAG, "Invalid path buffer");
+        return;
+    }
     // Store the entered URL from temp_buffer_path to path
     strncpy(app->path, app->temp_buffer_path, app->temp_buffer_size_path - 1);
 
@@ -236,7 +313,7 @@ static void web_crawler_set_path_updated(void *context)
         variable_item_set_current_value_text(app->path_item, app->path);
 
         // Save the URL to the settings
-        save_settings(app->path, app->ssid, app->password);
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type);
 
         // send to UART
         if (!flipper_http_save_wifi(app->ssid, app->password))
@@ -249,7 +326,7 @@ static void web_crawler_set_path_updated(void *context)
     }
 
     // Return to the Configure view
-    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewConfigure);
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListRequest);
 }
 
 /**
@@ -259,7 +336,16 @@ static void web_crawler_set_path_updated(void *context)
 static void web_crawler_set_ssid_updated(void *context)
 {
     WebCrawlerApp *app = (WebCrawlerApp *)context;
-    furi_check(app);
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    if (!app->temp_buffer_ssid || !app->temp_buffer_size_ssid || !app->ssid || !app->ssid_item)
+    {
+        FURI_LOG_E(TAG, "Invalid SSID buffer");
+        return;
+    }
     // Store the entered SSID from temp_buffer_ssid to ssid
     strncpy(app->ssid, app->temp_buffer_ssid, app->temp_buffer_size_ssid - 1);
 
@@ -268,7 +354,7 @@ static void web_crawler_set_ssid_updated(void *context)
         variable_item_set_current_value_text(app->ssid_item, app->ssid);
 
         // Save the SSID to the settings
-        save_settings(app->path, app->ssid, app->password);
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type);
 
         // send to UART
         if (!flipper_http_save_wifi(app->ssid, app->password))
@@ -281,7 +367,7 @@ static void web_crawler_set_ssid_updated(void *context)
     }
 
     // Return to the Configure view
-    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewConfigure);
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListWifi);
 }
 
 /**
@@ -291,7 +377,16 @@ static void web_crawler_set_ssid_updated(void *context)
 static void web_crawler_set_password_update(void *context)
 {
     WebCrawlerApp *app = (WebCrawlerApp *)context;
-    furi_check(app);
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    if (!app->temp_buffer_password || !app->temp_buffer_size_password || !app->password || !app->password_item)
+    {
+        FURI_LOG_E(TAG, "Invalid password buffer");
+        return;
+    }
     // Store the entered Password from temp_buffer_password to password
     strncpy(app->password, app->temp_buffer_password, app->temp_buffer_size_password - 1);
 
@@ -300,7 +395,7 @@ static void web_crawler_set_password_update(void *context)
         variable_item_set_current_value_text(app->password_item, app->password);
 
         // Save the Password to the settings
-        save_settings(app->path, app->ssid, app->password);
+        save_settings(app->path, app->ssid, app->password, app->file_rename, app->file_type);
 
         // send to UART
         if (!flipper_http_save_wifi(app->ssid, app->password))
@@ -313,7 +408,81 @@ static void web_crawler_set_password_update(void *context)
     }
 
     // Return to the Configure view
-    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewConfigure);
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListWifi);
+}
+
+/**
+ * @brief      Callback for when the user finishes entering the File Type.
+ * @param      context   The context - WebCrawlerApp object.
+ */
+static void web_crawler_set_file_type_update(void *context)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    if (!app->temp_buffer_file_type || !app->temp_buffer_size_file_type || !app->file_type || !app->file_type_item)
+    {
+        FURI_LOG_E(TAG, "Invalid file type buffer");
+        return;
+    }
+    // Temporary buffer to store the old name
+    char old_file_type[256];
+
+    strncpy(old_file_type, app->file_type, sizeof(old_file_type) - 1);
+    old_file_type[sizeof(old_file_type) - 1] = '\0'; // Null-terminate
+    strncpy(app->file_type, app->temp_buffer_file_type, app->temp_buffer_size_file_type - 1);
+
+    if (app->file_type_item)
+    {
+        variable_item_set_current_value_text(app->file_type_item, app->file_type);
+    }
+
+    rename_received_data(app->file_rename, app->file_rename, app->file_type, old_file_type);
+
+    // Return to the Configure view
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListFile);
+}
+
+/**
+ * @brief      Callback for when the user finishes entering the File Rename.
+ * @param      context   The context - WebCrawlerApp object.
+ */
+static void web_crawler_set_file_rename_update(void *context)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    if (!app->temp_buffer_file_rename || !app->temp_buffer_size_file_rename || !app->file_rename || !app->file_rename_item)
+    {
+        FURI_LOG_E(TAG, "Invalid file rename buffer");
+        return;
+    }
+
+    // Temporary buffer to store the old name
+    char old_name[256];
+
+    // Ensure that app->file_rename is null-terminated
+    strncpy(old_name, app->file_rename, sizeof(old_name) - 1);
+    old_name[sizeof(old_name) - 1] = '\0'; // Null-terminate
+
+    // Store the entered File Rename from temp_buffer_file_rename to file_rename
+    strncpy(app->file_rename, app->temp_buffer_file_rename, app->temp_buffer_size_file_rename - 1);
+
+    if (app->file_rename_item)
+    {
+        variable_item_set_current_value_text(app->file_rename_item, app->file_rename);
+    }
+
+    rename_received_data(old_name, app->file_rename, app->file_type, app->file_type);
+
+    // Return to the Configure view
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewVariableItemListFile);
 }
 
 /**
@@ -324,13 +493,23 @@ static void web_crawler_set_password_update(void *context)
 static void web_crawler_setting_item_path_clicked(void *context, uint32_t index)
 {
     WebCrawlerApp *app = (WebCrawlerApp *)context;
-    furi_check(app);
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    if (!app->text_input_path)
+    {
+        FURI_LOG_E(TAG, "Text input is NULL");
+        return;
+    }
+
     UNUSED(index);
     // Set up the text input
     text_input_set_header_text(app->text_input_path, "Enter URL");
 
     // Initialize temp_buffer with existing path
-    if (app->path)
+    if (app->path && strlen(app->path) > 0)
     {
         strncpy(app->temp_buffer_path, app->path, app->temp_buffer_size_path - 1);
     }
@@ -354,7 +533,7 @@ static void web_crawler_setting_item_path_clicked(void *context, uint32_t index)
     // Set the previous callback to return to Configure screen
     view_set_previous_callback(
         text_input_get_view(app->text_input_path),
-        web_crawler_back_to_configure_callback);
+        web_crawler_back_to_request_callback);
 
     // Show text input dialog
     view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewTextInput);
@@ -368,13 +547,22 @@ static void web_crawler_setting_item_path_clicked(void *context, uint32_t index)
 static void web_crawler_setting_item_ssid_clicked(void *context, uint32_t index)
 {
     WebCrawlerApp *app = (WebCrawlerApp *)context;
-    furi_check(app);
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
     UNUSED(index);
+    if (!app->text_input_ssid)
+    {
+        FURI_LOG_E(TAG, "Text input is NULL");
+        return;
+    }
     // Set up the text input
     text_input_set_header_text(app->text_input_ssid, "Enter SSID");
 
     // Initialize temp_buffer with existing SSID
-    if (app->ssid)
+    if (app->ssid && strlen(app->ssid) > 0)
     {
         strncpy(app->temp_buffer_ssid, app->ssid, app->temp_buffer_size_ssid - 1);
     }
@@ -398,7 +586,7 @@ static void web_crawler_setting_item_ssid_clicked(void *context, uint32_t index)
     // Set the previous callback to return to Configure screen
     view_set_previous_callback(
         text_input_get_view(app->text_input_ssid),
-        web_crawler_back_to_configure_callback);
+        web_crawler_back_to_wifi_callback);
 
     // Show text input dialog
     view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewTextInputSSID);
@@ -412,8 +600,17 @@ static void web_crawler_setting_item_ssid_clicked(void *context, uint32_t index)
 static void web_crawler_setting_item_password_clicked(void *context, uint32_t index)
 {
     WebCrawlerApp *app = (WebCrawlerApp *)context;
-    furi_check(app);
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
     UNUSED(index);
+    if (!app->text_input_password)
+    {
+        FURI_LOG_E(TAG, "Text input is NULL");
+        return;
+    }
     // Set up the text input
     text_input_set_header_text(app->text_input_password, "Enter Password");
 
@@ -434,8 +631,176 @@ static void web_crawler_setting_item_password_clicked(void *context, uint32_t in
     // Set the previous callback to return to Configure screen
     view_set_previous_callback(
         text_input_get_view(app->text_input_password),
-        web_crawler_back_to_configure_callback);
+        web_crawler_back_to_wifi_callback);
 
     // Show text input dialog
     view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewTextInputPassword);
+}
+
+/**
+ * @brief      Handler for File Type configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+static void web_crawler_setting_item_file_type_clicked(void *context, uint32_t index)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    UNUSED(index);
+    if (!app->text_input_file_type)
+    {
+        FURI_LOG_E(TAG, "Text input is NULL");
+        return;
+    }
+    // Set up the text input
+    text_input_set_header_text(app->text_input_file_type, "Enter File Type");
+
+    // Initialize temp_buffer with existing file_type
+    if (app->file_type && strlen(app->file_type) > 0)
+    {
+        strncpy(app->temp_buffer_file_type, app->file_type, app->temp_buffer_size_file_type - 1);
+    }
+    else
+    {
+        strncpy(app->temp_buffer_file_type, ".txt", app->temp_buffer_size_file_type - 1);
+    }
+
+    app->temp_buffer_file_type[app->temp_buffer_size_file_type - 1] = '\0';
+
+    // Configure the text input
+    bool clear_previous_text = false;
+    text_input_set_result_callback(
+        app->text_input_file_type,
+        web_crawler_set_file_type_update,
+        app,
+        app->temp_buffer_file_type,
+        app->temp_buffer_size_file_type,
+        clear_previous_text);
+
+    // Set the previous callback to return to Configure screen
+    view_set_previous_callback(
+        text_input_get_view(app->text_input_file_type),
+        web_crawler_back_to_file_callback);
+
+    // Show text input dialog
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewTextInputFileType);
+}
+
+/**
+ * @brief      Handler for File Rename configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+static void web_crawler_setting_item_file_rename_clicked(void *context, uint32_t index)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    UNUSED(index);
+    if (!app->text_input_file_rename)
+    {
+        FURI_LOG_E(TAG, "Text input is NULL");
+        return;
+    }
+    // Set up the text input
+    text_input_set_header_text(app->text_input_file_rename, "Enter File Rename");
+
+    // Initialize temp_buffer with existing file_rename
+    if (app->file_rename && strlen(app->file_rename) > 0)
+    {
+        strncpy(app->temp_buffer_file_rename, app->file_rename, app->temp_buffer_size_file_rename - 1);
+    }
+    else
+    {
+        strncpy(app->temp_buffer_file_rename, "received_data", app->temp_buffer_size_file_rename - 1);
+    }
+
+    app->temp_buffer_file_rename[app->temp_buffer_size_file_rename - 1] = '\0';
+
+    // Configure the text input
+    bool clear_previous_text = false;
+    text_input_set_result_callback(
+        app->text_input_file_rename,
+        web_crawler_set_file_rename_update,
+        app,
+        app->temp_buffer_file_rename,
+        app->temp_buffer_size_file_rename,
+        clear_previous_text);
+
+    // Set the previous callback to return to Configure screen
+    view_set_previous_callback(
+        text_input_get_view(app->text_input_file_rename),
+        web_crawler_back_to_file_callback);
+
+    // Show text input dialog
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewTextInputFileRename);
+}
+
+/**
+ * @brief      Handler for File Delete configuration item click.
+ * @param      context  The context - WebCrawlerApp object.
+ * @param      index    The index of the item that was clicked.
+ */
+static void web_crawler_setting_item_file_delete_clicked(void *context, uint32_t index)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    UNUSED(index);
+
+    if (!delete_received_data(app))
+    {
+        FURI_LOG_E(TAG, "Failed to delete file");
+    }
+
+    // Set the previous callback to return to Configure screen
+    view_set_previous_callback(
+        widget_get_view(app->widget_file_delete),
+        web_crawler_back_to_file_callback);
+
+    // Show text input dialog
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewFileDelete);
+}
+
+static void web_crawler_setting_item_file_read_clicked(void *context, uint32_t index)
+{
+    WebCrawlerApp *app = (WebCrawlerApp *)context;
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return;
+    }
+    UNUSED(index);
+
+    if (!load_received_data(app))
+    {
+        if (app->widget_file_read)
+        {
+            widget_reset(app->widget_file_read);
+            widget_add_text_scroll_element(
+                app->widget_file_read,
+                0,
+                0,
+                128,
+                64, "File is empty.");
+        }
+    }
+
+    // Set the previous callback to return to Configure screen
+    view_set_previous_callback(
+        widget_get_view(app->widget_file_read),
+        web_crawler_back_to_file_callback);
+
+    // Show text input dialog
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewFileRead);
 }

+ 50 - 25
web_crawler_e.h

@@ -20,44 +20,69 @@
 // Define the submenu items for our WebCrawler application
 typedef enum
 {
-    WebCrawlerSubmenuIndexRun,
-    WebCrawlerSubmenuIndexAbout,
-    WebCrawlerSubmenuIndexSetPath,
-    WebCrawlerSubmenuIndexData,
+    WebCrawlerSubmenuIndexRun,     // click to go to Run the GET request
+    WebCrawlerSubmenuIndexAbout,   // click to go to About screen
+    WebCrawlerSubmenuIndexConfig,  // click to go to Config submenu (Wifi, File)
+    WebCrawlerSubmenuIndexRequest, // click to go to Request submenu (Set URL, HTTP Method, Headers)
+    WebCrawlerSubmenuIndexWifi,    // click to go to Wifi submenu (SSID, Password)
+    WebCrawlerSubmenuIndexFile,    // click to go to file submenu (Read, File Type, Rename, Delete)
 } WebCrawlerSubmenuIndex;
 
-// Define views for our WebCrawler application
 typedef enum
 {
-    WebCrawlerViewSubmenu,           // Submenu
-    WebCrawlerViewAbout,             // About screen
-    WebCrawlerViewConfigure,         // Configuration screen
-    WebCrawlerViewTextInput,         // Text input for Path
-    WebCrawlerViewTextInputSSID,     // Text input for SSID
-    WebCrawlerViewTextInputPassword, // Text input for Password
-    WebCrawlerViewRun,               // Main run view
-    WebCrawlerViewData,              // Data view
-} WebCrawlerView;
+    WebCrawlerViewRun,                     // Run the GET request
+    WebCrawlerViewAbout,                   // About screen
+    WebCrawlerViewSubmenuConfig,           // Submenu Config view for App (Wifi, File)
+    WebCrawlerViewVariableItemListRequest, // Submenu for URL (Set URL, HTTP Method, Headers)
+    WebCrawlerViewVariableItemListWifi,    // Wifi Configuration screen (Submenu for SSID, Password)
+    WebCrawlerViewVariableItemListFile,    // Submenu for File (Read, File Type, Rename, Delete)
+    WebCrawlerViewMain,                    // Main view for App
+    WebCrawlerViewSubmenuMain,             // Submenu Main view for App (Run, About, Config)
+    WebCrawlerViewTextInput,               // Text input for Path
+    WebCrawlerViewTextInputSSID,           // Text input for SSID
+    WebCrawlerViewTextInputPassword,       // Text input for Password
+    WebCrawlerViewFileRead,                // Text input for File Read
+    WebCrawlerViewTextInputFileType,       // Text input for File Type
+    WebCrawlerViewTextInputFileRename,     // Text input for File Rename
+    WebCrawlerViewFileDelete,              // File Delete
+} WebCrawlerViewIndex;
 
 // Define the application structure
 typedef struct
 {
     ViewDispatcher *view_dispatcher;
     View *view_main;
-    Submenu *submenu;
+    View *view_run;
+    Submenu *submenu_main;
+    Submenu *submenu_config;
     Widget *widget_about;
+
     TextInput *text_input_path;
     TextInput *text_input_ssid;
     TextInput *text_input_password;
-    Widget *textbox;
-    VariableItemList *variable_item_list_config;
+    TextInput *text_input_file_type;
+    TextInput *text_input_file_rename;
+
+    Widget *widget_file_read;
+    Widget *widget_file_delete;
+
+    VariableItemList *variable_item_list_wifi;
+    VariableItemList *variable_item_list_file;
+    VariableItemList *variable_item_list_request;
 
-    char *path;
-    char *ssid;
-    char *password;
     VariableItem *path_item;
     VariableItem *ssid_item;
     VariableItem *password_item;
+    VariableItem *file_type_item;
+    VariableItem *file_rename_item;
+    VariableItem *file_read_item;
+    VariableItem *file_delete_item;
+
+    char *path;
+    char *ssid;
+    char *password;
+    char *file_type;
+    char *file_rename;
 
     char *temp_buffer_path;
     uint32_t temp_buffer_size_path;
@@ -67,11 +92,11 @@ typedef struct
 
     char *temp_buffer_password;
     uint32_t temp_buffer_size_password;
-} WebCrawlerApp;
 
-// forware declaration of storage functions
-static void save_settings(const char *path, const char *ssid, const char *password);
-static bool load_settings(char *path, size_t path_size, char *ssid, size_t ssid_size, char *password, size_t password_size, WebCrawlerApp *app);
-static bool load_received_data();
+    char *temp_buffer_file_type;
+    uint32_t temp_buffer_size_file_type;
 
+    char *temp_buffer_file_rename;
+    uint32_t temp_buffer_size_file_rename;
+} WebCrawlerApp;
 #endif // WEB_CRAWLER_E

+ 125 - 25
web_crawler_free.h

@@ -1,3 +1,5 @@
+// web_crawler_free.h
+
 static void free_buffers(WebCrawlerApp *app)
 {
     if (!app)
@@ -40,6 +42,30 @@ static void free_buffers(WebCrawlerApp *app)
         free(app->temp_buffer_password);
         app->temp_buffer_password = NULL;
     }
+
+    if (app->file_type)
+    {
+        free(app->file_type);
+        app->file_type = NULL;
+    }
+
+    if (app->temp_buffer_file_type)
+    {
+        free(app->temp_buffer_file_type);
+        app->temp_buffer_file_type = NULL;
+    }
+
+    if (app->file_rename)
+    {
+        free(app->file_rename);
+        app->file_rename = NULL;
+    }
+
+    if (app->temp_buffer_file_rename)
+    {
+        free(app->temp_buffer_file_rename);
+        app->temp_buffer_file_rename = NULL;
+    }
 }
 
 static void free_resources(WebCrawlerApp *app)
@@ -51,24 +77,50 @@ static void free_resources(WebCrawlerApp *app)
     }
 
     free_buffers(app);
-    free(app);
 }
 
 static void free_all(WebCrawlerApp *app, char *reason)
 {
-    FURI_LOG_E(TAG, reason);
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "Invalid app context");
+        return;
+    }
+    if (reason)
+    {
+        FURI_LOG_I(TAG, reason);
+    }
+
     if (app->view_main)
         view_free(app->view_main);
-    if (app->submenu)
-        submenu_free(app->submenu);
-    if (app->variable_item_list_config)
-        variable_item_list_free(app->variable_item_list_config);
+    if (app->submenu_main)
+        submenu_free(app->submenu_main);
+    if (app->submenu_config)
+        submenu_free(app->submenu_config);
+    if (app->variable_item_list_wifi)
+        variable_item_list_free(app->variable_item_list_wifi);
+    if (app->variable_item_list_file)
+        variable_item_list_free(app->variable_item_list_file);
+    if (app->variable_item_list_request)
+        variable_item_list_free(app->variable_item_list_request);
     if (app->view_dispatcher)
         view_dispatcher_free(app->view_dispatcher);
     if (app->widget_about)
         widget_free(app->widget_about);
-    if (app->textbox)
-        widget_free(app->textbox);
+    if (app->widget_file_read)
+        widget_free(app->widget_file_read);
+    if (app->widget_file_delete)
+        widget_free(app->widget_file_delete);
+    if (app->text_input_path)
+        text_input_free(app->text_input_path);
+    if (app->text_input_ssid)
+        text_input_free(app->text_input_ssid);
+    if (app->text_input_password)
+        text_input_free(app->text_input_password);
+    if (app->text_input_file_type)
+        text_input_free(app->text_input_file_type);
+    if (app->text_input_file_rename)
+        text_input_free(app->text_input_file_rename);
 
     furi_record_close(RECORD_GUI);
     free_resources(app);
@@ -85,9 +137,23 @@ static void web_crawler_app_free(WebCrawlerApp *app)
         return;
     }
 
+    if (!app->view_dispatcher)
+    {
+        FURI_LOG_E(TAG, "Invalid view dispatcher");
+        return;
+    }
+
     // Remove and free Main view
-    view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewRun);
-    view_free(app->view_main);
+    if (app->view_main)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewMain);
+        view_free(app->view_main);
+    }
+    if (app->view_run)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewRun);
+        view_free(app->view_run);
+    }
 
     // diconnect wifi
     if (!flipper_http_disconnect_wifi())
@@ -99,30 +165,64 @@ static void web_crawler_app_free(WebCrawlerApp *app)
     flipper_http_deinit();
 
     // Remove and free Submenu
-    view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewSubmenu);
-    submenu_free(app->submenu);
-
+    if (app->submenu_main)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewSubmenuMain);
+        submenu_free(app->submenu_main);
+    }
+    if (app->submenu_config)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewSubmenuConfig);
+        submenu_free(app->submenu_config);
+    }
     // Remove and free Configuration screen
-    view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewConfigure);
-    variable_item_list_free(app->variable_item_list_config);
+    if (app->variable_item_list_wifi)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewVariableItemListWifi);
+        variable_item_list_free(app->variable_item_list_wifi);
+    }
+    if (app->variable_item_list_file)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewVariableItemListFile);
+        variable_item_list_free(app->variable_item_list_file);
+    }
+    if (app->variable_item_list_request)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewVariableItemListRequest);
+        variable_item_list_free(app->variable_item_list_request);
+    }
 
     // Remove and free Text Input views
     view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInput);
     view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInputSSID);
     view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInputPassword);
+    view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInputFileType);
+    view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewTextInputFileRename);
 
-    // Remove and free About view
-    view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewAbout);
-    widget_free(app->widget_about);
-
-    // Remove and free Textbox view
-    view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewData);
-    widget_free(app->textbox);
+    // Remove and free Widgets
+    if (app->widget_about)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewAbout);
+        widget_free(app->widget_about);
+    }
+    if (app->widget_file_read)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewFileRead);
+        widget_free(app->widget_file_read);
+    }
+    if (app->widget_file_delete)
+    {
+        view_dispatcher_remove_view(app->view_dispatcher, WebCrawlerViewFileDelete);
+        widget_free(app->widget_file_delete);
+    }
 
     // Free the ViewDispatcher and close GUI
-    view_dispatcher_free(app->view_dispatcher);
+    if (app->view_dispatcher)
+        view_dispatcher_free(app->view_dispatcher);
 
     // Free the application structure
-    free_buffers(app);
-    free(app);
+    if (app)
+    {
+        free(app);
+    }
 }

+ 153 - 152
web_crawler_i.h

@@ -1,11 +1,10 @@
 #ifndef WEB_CRAWLER_I_H
 #define WEB_CRAWLER_I_H
-
 /**
  * @brief      Function to allocate resources for the WebCrawlerApp.
  * @return     Pointer to the initialized WebCrawlerApp, or NULL on failure.
  */
-static WebCrawlerApp *web_crawler_app_alloc()
+WebCrawlerApp *web_crawler_app_alloc()
 {
     // Initialize the entire structure to zero to prevent undefined behavior
     WebCrawlerApp *app = (WebCrawlerApp *)malloc(sizeof(WebCrawlerApp));
@@ -110,218 +109,220 @@ static WebCrawlerApp *web_crawler_app_alloc()
     }
     app->password[0] = '\0';
 
-    // Allocate TextInput views
-    app->text_input_path = text_input_alloc();
-    if (!app->text_input_path)
+    // Allocate and initialize temp_buffer_file_type
+    app->temp_buffer_size_file_type = 128;
+    app->temp_buffer_file_type = malloc(app->temp_buffer_size_file_type);
+    if (!app->temp_buffer_file_type)
     {
-        free_all(app, "Failed to allocate TextInput for Path");
+        FURI_LOG_E(TAG, "Failed to allocate temp_buffer_file_type");
+        free_all(app, "Failed to allocate temp_buffer_file_type");
         return NULL;
     }
 
-    app->text_input_ssid = text_input_alloc();
-    if (!app->text_input_ssid)
+    // Allocate file_type
+    app->file_type = malloc(app->temp_buffer_size_file_type);
+    if (!app->file_type)
     {
-        free_all(app, "Failed to allocate TextInput for SSID");
+        FURI_LOG_E(TAG, "Failed to allocate file_type");
+        free_all(app, "Failed to allocate file_type");
         return NULL;
     }
+    app->file_type[0] = '\0';
 
-    app->text_input_password = text_input_alloc();
-    if (!app->text_input_password)
+    // Allocate and intialize temp_buffer_file_rename
+    app->temp_buffer_size_file_rename = 128;
+    app->temp_buffer_file_rename = malloc(app->temp_buffer_size_file_rename);
+    if (!app->temp_buffer_file_rename)
     {
-        free_all(app, "Failed to allocate TextInput for Password");
+        FURI_LOG_E(TAG, "Failed to allocate temp_buffer_file_rename");
+        free_all(app, "Failed to allocate temp_buffer_file_rename");
         return NULL;
     }
 
-    // Add TextInput views with unique view IDs
-    view_dispatcher_add_view(
-        app->view_dispatcher,
-        WebCrawlerViewTextInput,
-        text_input_get_view(app->text_input_path));
-    view_dispatcher_add_view(
-        app->view_dispatcher,
-        WebCrawlerViewTextInputSSID,
-        text_input_get_view(app->text_input_ssid));
-    view_dispatcher_add_view(
-        app->view_dispatcher,
-        WebCrawlerViewTextInputPassword,
-        text_input_get_view(app->text_input_password));
-
-    // Set previous callback for TextInput views to return to Configure screen
-    view_set_previous_callback(
-        text_input_get_view(app->text_input_path),
-        web_crawler_back_to_configure_callback);
-    view_set_previous_callback(
-        text_input_get_view(app->text_input_ssid),
-        web_crawler_back_to_configure_callback);
-    view_set_previous_callback(
-        text_input_get_view(app->text_input_password),
-        web_crawler_back_to_configure_callback);
-
-    // Allocate Configuration screen
-    app->variable_item_list_config = variable_item_list_alloc();
-    if (!app->variable_item_list_config)
+    // Allocate file_rename
+    app->file_rename = malloc(app->temp_buffer_size_file_rename);
+    if (!app->file_rename)
     {
-        free_all(app, "Failed to allocate VariableItemList for Configuration");
+        FURI_LOG_E(TAG, "Failed to allocate file_rename");
+        free_all(app, "Failed to allocate file_rename");
         return NULL;
     }
-    variable_item_list_reset(app->variable_item_list_config);
-
-    // Add "Path" item to the configuration screen
-    app->path_item = variable_item_list_add(
-        app->variable_item_list_config,
-        "Path",
-        1,    // Number of possible values (1 for a single text value)
-        NULL, // No change callback needed
-        NULL  // No context needed
-    );
-    if (!app->path_item)
+    app->file_rename[0] = '\0';
+
+    // Allocate TextInput views
+    app->text_input_path = text_input_alloc();
+    app->text_input_ssid = text_input_alloc();
+    app->text_input_password = text_input_alloc();
+    app->text_input_file_type = text_input_alloc();
+    app->text_input_file_rename = text_input_alloc();
+    if (!app->text_input_path || !app->text_input_ssid || !app->text_input_password || !app->text_input_file_type || !app->text_input_file_rename)
     {
-        free_all(app, "Failed to add Path item to VariableItemList");
+        FURI_LOG_E(TAG, "Failed to allocate TextInput");
+        free_all(app, "Failed to allocate TextInput");
         return NULL;
     }
-    variable_item_set_current_value_text(app->path_item, ""); // Initialize
-
-    // Add "SSID" item to the configuration screen
-    app->ssid_item = variable_item_list_add(
-        app->variable_item_list_config,
-        "SSID",
-        1,    // Number of possible values (1 for a single text value)
-        NULL, // No change callback needed
-        NULL  // No context needed
-    );
-    if (!app->ssid_item)
+
+    // Add TextInput views with unique view IDs
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInput, text_input_get_view(app->text_input_path));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputSSID, text_input_get_view(app->text_input_ssid));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputPassword, text_input_get_view(app->text_input_password));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputFileType, text_input_get_view(app->text_input_file_type));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewTextInputFileRename, text_input_get_view(app->text_input_file_rename));
+
+    // Set previous callback for TextInput views to return to Configure screen
+    view_set_previous_callback(text_input_get_view(app->text_input_path), web_crawler_back_to_request_callback);
+    view_set_previous_callback(text_input_get_view(app->text_input_ssid), web_crawler_back_to_wifi_callback);
+    view_set_previous_callback(text_input_get_view(app->text_input_password), web_crawler_back_to_wifi_callback);
+    view_set_previous_callback(text_input_get_view(app->text_input_file_type), web_crawler_back_to_file_callback);
+    view_set_previous_callback(text_input_get_view(app->text_input_file_rename), web_crawler_back_to_file_callback);
+
+    // Allocate Configuration screen
+    app->variable_item_list_wifi = variable_item_list_alloc();
+    app->variable_item_list_file = variable_item_list_alloc();
+    app->variable_item_list_request = variable_item_list_alloc();
+    if (!app->variable_item_list_wifi || !app->variable_item_list_file || !app->variable_item_list_request)
     {
-        free_all(app, "Failed to add SSID item to VariableItemList");
+        FURI_LOG_E(TAG, "Failed to allocate VariableItemList");
+        free_all(app, "Failed to allocate VariableItemList");
         return NULL;
     }
-    variable_item_set_current_value_text(app->ssid_item, ""); // Initialize
-
-    // Add "Password" item to the configuration screen
-    app->password_item = variable_item_list_add(
-        app->variable_item_list_config,
-        "Password",
-        1,    // Number of possible values (1 for a single text value)
-        NULL, // No change callback needed
-        NULL  // No context needed
-    );
-    if (!app->password_item)
+    variable_item_list_reset(app->variable_item_list_wifi);
+    variable_item_list_reset(app->variable_item_list_file);
+    variable_item_list_reset(app->variable_item_list_request);
+
+    // Add item to the configuration screen
+    app->path_item = variable_item_list_add(app->variable_item_list_request, "Path", 0, NULL, NULL);
+    //
+    app->ssid_item = variable_item_list_add(app->variable_item_list_wifi, "SSID", 0, NULL, NULL);         // index 0
+    app->password_item = variable_item_list_add(app->variable_item_list_wifi, "Password", 0, NULL, NULL); // index 1
+    //
+    app->file_read_item = variable_item_list_add(app->variable_item_list_file, "Read File", 0, NULL, NULL);     // index 0
+    app->file_type_item = variable_item_list_add(app->variable_item_list_file, "Set File Type", 0, NULL, NULL); // index 1
+    app->file_rename_item = variable_item_list_add(app->variable_item_list_file, "Rename File", 0, NULL, NULL); // index 2
+    app->file_delete_item = variable_item_list_add(app->variable_item_list_file, "Delete File", 0, NULL, NULL); // index 3
+
+    if (!app->ssid_item || !app->password_item || !app->file_type_item || !app->file_rename_item || !app->path_item || !app->file_read_item || !app->file_delete_item)
     {
-        free_all(app, "Failed to add Password item to VariableItemList");
+        free_all(app, "Failed to add items to VariableItemList");
         return NULL;
     }
-    variable_item_set_current_value_text(app->password_item, ""); // Initialize
-
-    // Set a single enter callback for all configuration items
-    variable_item_list_set_enter_callback(
-        app->variable_item_list_config,
-        web_crawler_config_enter_callback,
-        app);
-
-    // Set previous callback for configuration screen
-    view_set_previous_callback(
-        variable_item_list_get_view(app->variable_item_list_config),
-        web_crawler_back_to_main_callback);
-
-    // Add Configuration view to ViewDispatcher
-    view_dispatcher_add_view(
-        app->view_dispatcher,
-        WebCrawlerViewConfigure,
-        variable_item_list_get_view(app->variable_item_list_config));
-
-    // Allocate Submenu view
-    app->submenu = submenu_alloc();
-    if (!app->submenu)
+
+    variable_item_set_current_value_text(app->path_item, "");        // Initialize
+    variable_item_set_current_value_text(app->ssid_item, "");        // Initialize
+    variable_item_set_current_value_text(app->password_item, "");    // Initialize
+    variable_item_set_current_value_text(app->file_type_item, "");   // Initialize
+    variable_item_set_current_value_text(app->file_rename_item, ""); // Initialize
+    variable_item_set_current_value_text(app->file_read_item, "");   // Initialize
+    variable_item_set_current_value_text(app->file_delete_item, ""); // Initialize
+
+    // Set a single callback for all items
+    variable_item_list_set_enter_callback(app->variable_item_list_wifi, web_crawler_wifi_enter_callback, app);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_wifi), web_crawler_back_to_configure_callback);
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewVariableItemListWifi, variable_item_list_get_view(app->variable_item_list_wifi));
+
+    variable_item_list_set_enter_callback(app->variable_item_list_file, web_crawler_file_enter_callback, app);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_file), web_crawler_back_to_configure_callback);
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewVariableItemListFile, variable_item_list_get_view(app->variable_item_list_file));
+
+    variable_item_list_set_enter_callback(app->variable_item_list_request, web_crawler_request_enter_callback, app);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_request), web_crawler_back_to_configure_callback);
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewVariableItemListRequest, variable_item_list_get_view(app->variable_item_list_request));
+    //------------------------------//
+    //        SUBMENU VIEW          //
+    //------------------------------//
+
+    // Allocate
+    app->submenu_main = submenu_alloc();
+    app->submenu_config = submenu_alloc();
+    if (!app->submenu_main || !app->submenu_config)
     {
         FURI_LOG_E(TAG, "Failed to allocate Submenu");
         free_all(app, "Failed to allocate Submenu");
         return NULL;
     }
 
-    submenu_set_header(app->submenu, "Web Crawler v0.2");
+    // Set header
+    submenu_set_header(app->submenu_main, "Web Crawler v0.3");
+    submenu_set_header(app->submenu_config, "Settings");
 
-    // Add items to Submenu
-    submenu_add_item(app->submenu, "Run", WebCrawlerSubmenuIndexRun, web_crawler_submenu_callback, app);
-    submenu_add_item(app->submenu, "About", WebCrawlerSubmenuIndexAbout, web_crawler_submenu_callback, app);
-    submenu_add_item(app->submenu, "Configure", WebCrawlerSubmenuIndexSetPath, web_crawler_submenu_callback, app);
-    submenu_add_item(app->submenu, "Read File", WebCrawlerSubmenuIndexData, web_crawler_submenu_callback, app);
+    // Add items
+    submenu_add_item(app->submenu_main, "Run", WebCrawlerSubmenuIndexRun, web_crawler_submenu_callback, app);
+    submenu_add_item(app->submenu_main, "About", WebCrawlerSubmenuIndexAbout, web_crawler_submenu_callback, app);
+    submenu_add_item(app->submenu_main, "Settings", WebCrawlerSubmenuIndexConfig, web_crawler_submenu_callback, app);
+
+    submenu_add_item(app->submenu_config, "WiFi", WebCrawlerSubmenuIndexWifi, web_crawler_submenu_callback, app);
+    submenu_add_item(app->submenu_config, "File", WebCrawlerSubmenuIndexFile, web_crawler_submenu_callback, app);
+    submenu_add_item(app->submenu_config, "Request", WebCrawlerSubmenuIndexRequest, web_crawler_submenu_callback, app);
 
     // Set previous callback for Submenu
-    view_set_previous_callback(submenu_get_view(app->submenu), web_crawler_exit_app_callback);
+    view_set_previous_callback(submenu_get_view(app->submenu_main), web_crawler_exit_app_callback);       // Exit App
+    view_set_previous_callback(submenu_get_view(app->submenu_config), web_crawler_back_to_main_callback); // Back to Main
 
     // Add Submenu view to ViewDispatcher
-    view_dispatcher_add_view(
-        app->view_dispatcher,
-        WebCrawlerViewSubmenu,
-        submenu_get_view(app->submenu));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewSubmenuMain, submenu_get_view(app->submenu_main));
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewSubmenuConfig, submenu_get_view(app->submenu_config));
+
+    //---------------------------------------------------------->
 
     // Allocate Main view
     app->view_main = view_alloc();
-    if (!app->view_main)
+    app->view_run = view_alloc();
+    if (!app->view_main || !app->view_run)
     {
-        free_all(app, "Failed to allocate Main view");
+        free_all(app, "Failed to allocate Views");
         return NULL;
     }
 
-    view_set_draw_callback(app->view_main, web_crawler_view_draw_callback);
-    view_set_previous_callback(app->view_main, web_crawler_back_to_main_callback);
+    // view_set_draw_callback(app->view_main, web_crawler_view_draw_callback);
+    view_set_previous_callback(app->view_main, web_crawler_exit_app_callback);
+
+    view_set_draw_callback(app->view_run, web_crawler_view_draw_callback);
+    view_set_previous_callback(app->view_run, web_crawler_back_to_main_callback);
 
     // Add Main view to ViewDispatcher
-    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewRun, app->view_main);
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewMain, app->view_main);
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewRun, app->view_run);
 
     //-- WIDGET ABOUT VIEW --
 
     // Allocate and add About view
     app->widget_about = widget_alloc();
-    if (!app->widget_about)
+    app->widget_file_read = widget_alloc();
+    app->widget_file_delete = widget_alloc();
+    if (!app->widget_about || !app->widget_file_read || !app->widget_file_delete)
     {
-        FURI_LOG_E(TAG, "Failed to allocate About widget");
-        free_all(app, "Failed to allocate About widget");
+        FURI_LOG_E(TAG, "Failed to allocate Widget");
+        free_all(app, "Failed to allocate Widget");
         return NULL;
     }
 
     // Reset the widget before adding elements
     widget_reset(app->widget_about);
+    widget_reset(app->widget_file_read);
+    widget_reset(app->widget_file_delete);
+
+    widget_add_text_scroll_element(app->widget_about, 0, 0, 128, 64, "Web Crawler App\n"
+                                                                     "---\n"
+                                                                     "This is a web crawler app for Flipper Zero.\n"
+                                                                     "---\n"
+                                                                     "Visit github.com/jblanked for more details.\n"
+                                                                     "---\n"
+                                                                     "Press BACK to return.");
 
-    widget_add_text_scroll_element(
-        app->widget_about,
-        0,
-        0,
-        128,
-        64,
-        "Web Crawler App\n"
-        "---\n"
-        "This is a web crawler app for Flipper Zero.\n"
-        "---\n"
-        "Visit github.com/jblanked for more details.\n"
-        "---\n"
-        "Press BACK to return.");
+    widget_add_text_scroll_element(app->widget_file_read, 0, 0, 128, 64, "Data will be displayed here.");
+    widget_add_text_scroll_element(app->widget_file_delete, 0, 0, 128, 64, "File deleted.");
 
     view_set_previous_callback(widget_get_view(app->widget_about), web_crawler_back_to_main_callback);
     view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewAbout, widget_get_view(app->widget_about));
 
-    //-- TEXTBOX DATA VIEW --
-    app->textbox = widget_alloc();
-    if (!app->textbox)
-    {
-        FURI_LOG_E(TAG, "Failed to allocate Textbox");
-        free_all(app, "Failed to allocate Textbox");
-        return NULL;
-    }
-
-    widget_reset(app->textbox);
-
-    widget_add_text_scroll_element(
-        app->textbox,
-        0,
-        0,
-        128,
-        64, "Data will be displayed here.");
+    view_set_previous_callback(widget_get_view(app->widget_file_read), web_crawler_back_to_file_callback);
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewFileRead, widget_get_view(app->widget_file_read));
 
-    view_set_previous_callback(widget_get_view(app->textbox), web_crawler_back_to_main_callback);
-    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewData, widget_get_view(app->textbox));
+    view_set_previous_callback(widget_get_view(app->widget_file_delete), web_crawler_back_to_file_callback);
+    view_dispatcher_add_view(app->view_dispatcher, WebCrawlerViewFileDelete, widget_get_view(app->widget_file_delete));
 
     // Load Settings and Update Views
-    if (!load_settings(app->path, app->temp_buffer_size_path, app->ssid, app->temp_buffer_size_ssid, app->password, app->temp_buffer_size_password, app))
+    if (!load_settings(app->path, app->temp_buffer_size_path, app->ssid, app->temp_buffer_size_ssid, app->password, app->temp_buffer_size_password, app->file_rename, app->temp_buffer_size_file_rename, app->file_type, app->temp_buffer_size_file_type, app))
     {
         FURI_LOG_E(TAG, "Failed to load settings");
     }
@@ -352,7 +353,7 @@ static WebCrawlerApp *web_crawler_app_alloc()
     app_instance = app;
 
     // Start with the Submenu view
-    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewSubmenu);
+    view_dispatcher_switch_to_view(app->view_dispatcher, WebCrawlerViewSubmenuMain);
 
     return app;
 }

+ 214 - 14
web_crawler_storage.h

@@ -5,7 +5,10 @@
 #include <storage/storage.h>
 
 #define SETTINGS_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/settings.bin"
-#define RECEIVED_DATA_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/received_data.txt"
+#define RECEIVED_DATA_PATH STORAGE_EXT_PATH_PREFIX "/apps_data/" http_tag "/" // add the file name to the end (e.g. "received_data.txt")
+
+// will need to make a duplicate of the file
+// one to save data, and the other for users to manipulate
 
 #define MAX_RECEIVED_DATA_SIZE 1024
 #define SHOW_MAX_FILE_SIZE 2048
@@ -14,7 +17,7 @@
 #define TRUNCATION_NOTICE "\n\n[Data truncated due to size limits]"
 
 // Function to save settings: path, SSID, and password
-static void save_settings(const char *path, const char *ssid, const char *password)
+static void save_settings(const char *path, const char *ssid, const char *password, const char *file_rename, const char *file_type)
 {
     // Create the directory for saving settings
     char directory_path[256];
@@ -34,6 +37,11 @@ static void save_settings(const char *path, const char *ssid, const char *passwo
         return;
     }
 
+    if (file_type == NULL || strlen(file_type) == 0)
+    {
+        file_type = ".txt";
+    }
+
     // Save the path length and data
     size_t path_length = strlen(path) + 1; // Include null terminator
     if (storage_file_write(file, &path_length, sizeof(size_t)) != sizeof(size_t) ||
@@ -58,7 +66,21 @@ static void save_settings(const char *path, const char *ssid, const char *passwo
         FURI_LOG_E(TAG, "Failed to write password");
     }
 
-    FURI_LOG_I(TAG, "Settings saved: path=%s, ssid=%s, password=%s", path, ssid, password);
+    // Save the file rename length and data
+    size_t file_rename_length = strlen(file_rename) + 1; // Include null terminator
+    if (storage_file_write(file, &file_rename_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, file_rename, file_rename_length) != file_rename_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write file rename");
+    }
+
+    // Save the file type length and data
+    size_t file_type_length = strlen(file_type) + 1; // Include null terminator
+    if (storage_file_write(file, &file_type_length, sizeof(size_t)) != sizeof(size_t) ||
+        storage_file_write(file, file_type, file_type_length) != file_type_length)
+    {
+        FURI_LOG_E(TAG, "Failed to write file type");
+    }
 
     storage_file_close(file);
     storage_file_free(file);
@@ -66,8 +88,24 @@ static void save_settings(const char *path, const char *ssid, const char *passwo
 }
 
 // Function to load settings: path, SSID, and password
-static bool load_settings(char *path, size_t path_size, char *ssid, size_t ssid_size, char *password, size_t password_size, WebCrawlerApp *app)
+static bool load_settings(
+    char *path,
+    size_t path_size,
+    char *ssid,
+    size_t ssid_size,
+    char *password,
+    size_t password_size,
+    char *file_rename,
+    size_t file_rename_size,
+    char *file_type,
+    size_t file_type_size,
+    WebCrawlerApp *app)
 {
+    if (!app)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return false;
+    }
     Storage *storage = furi_record_open(RECORD_STORAGE);
     File *file = storage_file_alloc(storage);
 
@@ -118,10 +156,38 @@ static bool load_settings(char *path, size_t path_size, char *ssid, size_t ssid_
     }
     password[password_length - 1] = '\0'; // Ensure null-termination
 
+    // Load the file rename
+    size_t file_rename_length;
+    if (storage_file_read(file, &file_rename_length, sizeof(size_t)) != sizeof(size_t) || file_rename_length > file_rename_size ||
+        storage_file_read(file, file_rename, file_rename_length) != file_rename_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read file rename");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+    file_rename[file_rename_length - 1] = '\0'; // Ensure null-termination
+
+    // Load the file type
+    size_t file_type_length;
+    if (storage_file_read(file, &file_type_length, sizeof(size_t)) != sizeof(size_t) || file_type_length > file_type_size ||
+        storage_file_read(file, file_type, file_type_length) != file_type_length)
+    {
+        FURI_LOG_E(TAG, "Failed to read file type");
+        storage_file_close(file);
+        storage_file_free(file);
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+    file_type[file_type_length - 1] = '\0'; // Ensure null-termination
+
     // set the path, ssid, and password
     strncpy(app->path, path, path_size);
     strncpy(app->ssid, ssid, ssid_size);
     strncpy(app->password, password, password_size);
+    strncpy(app->file_rename, file_rename, file_rename_size);
+    strncpy(app->file_type, file_type, file_type_size);
 
     storage_file_close(file);
     storage_file_free(file);
@@ -129,6 +195,140 @@ static bool load_settings(char *path, size_t path_size, char *ssid, size_t ssid_
     return true;
 }
 
+static bool delete_received_data(WebCrawlerApp *app)
+{
+    if (app == NULL)
+    {
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
+        return false;
+    }
+    // Open the storage record
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    if (!storage)
+    {
+        FURI_LOG_E(TAG, "Failed to open storage record");
+        return false;
+    }
+
+    if (!storage_simply_remove_recursive(storage, RECEIVED_DATA_PATH "received_data.txt"))
+    {
+        FURI_LOG_E(TAG, "Failed to delete main file");
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    // Allocate memory for new_path
+    char *new_path = malloc(256);
+    if (new_path == NULL)
+    {
+        FURI_LOG_E(TAG, "Memory allocation failed for paths");
+        free(new_path);
+        return false;
+    }
+
+    if (app->file_type == NULL || strlen(app->file_type) == 0)
+    {
+        app->file_type = ".txt";
+    }
+
+    // Format the new_path
+    int ret_new = snprintf(new_path, 256, "%s%s%s", RECEIVED_DATA_PATH, app->file_rename, app->file_type);
+    if (ret_new < 0 || (size_t)ret_new >= 256)
+    {
+        FURI_LOG_E(TAG, "Failed to create new_path");
+        free(new_path);
+        return false;
+    }
+
+    if (!storage_simply_remove_recursive(storage, new_path))
+    {
+        FURI_LOG_E(TAG, "Failed to delete duplicate file");
+        furi_record_close(RECORD_STORAGE);
+        return false;
+    }
+
+    furi_record_close(RECORD_STORAGE);
+
+    return true;
+}
+
+static bool rename_received_data(const char *old_name, const char *new_name, const char *file_type, const char *old_file_type)
+{
+    // Open the storage record
+    Storage *storage = furi_record_open(RECORD_STORAGE);
+    if (!storage)
+    {
+        FURI_LOG_E(TAG, "Failed to open storage record");
+        return false;
+    }
+    // Allocate memory for old_path and new_path
+    char *new_path = malloc(256);
+    char *old_path = malloc(256);
+    if (new_path == NULL || old_path == NULL)
+    {
+        FURI_LOG_E(TAG, "Memory allocation failed for paths");
+        free(old_path);
+        free(new_path);
+        return false;
+    }
+
+    if (file_type == NULL || strlen(file_type) == 0)
+    {
+        file_type = ".txt";
+    }
+    if (old_file_type == NULL || strlen(old_file_type) == 0)
+    {
+        old_file_type = ".txt";
+    }
+
+    // Format the old_path
+    int ret_old = snprintf(old_path, 256, "%s%s%s", RECEIVED_DATA_PATH, old_name, old_file_type);
+    if (ret_old < 0 || (size_t)ret_old >= 256)
+    {
+        FURI_LOG_E(TAG, "Failed to create old_path");
+        free(old_path);
+        free(new_path);
+        return false;
+    }
+
+    // Format the new_path
+    int ret_new = snprintf(new_path, 256, "%s%s%s", RECEIVED_DATA_PATH, new_name, file_type);
+    if (ret_new < 0 || (size_t)ret_new >= 256)
+    {
+        FURI_LOG_E(TAG, "Failed to create new_path");
+        free(old_path);
+        free(new_path);
+        return false;
+    }
+
+    // Check if the file exists
+    if (!storage_file_exists(storage, old_path))
+    {
+        if (!storage_file_exists(storage, RECEIVED_DATA_PATH "received_data.txt"))
+        {
+            FURI_LOG_E(TAG, "No saved file exists");
+            free(old_path);
+            free(new_path);
+            furi_record_close(RECORD_STORAGE);
+            return false;
+        }
+        else
+        {
+            bool renamed = storage_common_copy(storage, RECEIVED_DATA_PATH "received_data.txt", new_path) == FSE_OK;
+
+            furi_record_close(RECORD_STORAGE);
+            return renamed;
+        }
+    }
+    else
+    {
+        bool renamed = storage_common_rename(storage, old_path, new_path) == FSE_OK;
+        storage_simply_remove_recursive(storage, old_path);
+        furi_record_close(RECORD_STORAGE);
+        return renamed;
+    }
+}
+
 static bool text_show_read_lines(File *file, FuriString *str_result)
 {
     // Reset the FuriString to ensure it's empty before reading
@@ -154,14 +354,15 @@ static bool text_show_read_lines(File *file, FuriString *str_result)
     return true;
 }
 
-static bool load_received_data()
+static bool load_received_data(WebCrawlerApp *app)
 {
-    if (!app_instance)
+    if (app == NULL)
     {
-        FURI_LOG_E(TAG, "App instance is NULL");
+        FURI_LOG_E(TAG, "WebCrawlerApp is NULL");
         return false;
     }
-    if (!app_instance->textbox)
+
+    if (!app->widget_file_read)
     {
         FURI_LOG_E(TAG, "Textbox is NULL");
         return false;
@@ -185,9 +386,8 @@ static bool load_received_data()
     }
 
     // Open the file for reading
-    if (!storage_file_open(file, RECEIVED_DATA_PATH, FSAM_READ, FSOM_OPEN_EXISTING))
+    if (!storage_file_open(file, RECEIVED_DATA_PATH "received_data.txt", FSAM_READ, FSOM_OPEN_EXISTING))
     {
-        FURI_LOG_E(TAG, "Failed to open received data file for reading: %s", RECEIVED_DATA_PATH);
         storage_file_free(file);
         furi_record_close(RECORD_STORAGE);
         return false; // Return false if the file does not exist
@@ -224,12 +424,12 @@ static bool load_received_data()
     const char *data_cstr = furi_string_get_cstr(str_result);
     // Set the text box with the received data
 
-    widget_reset(app_instance->textbox);
-    FURI_LOG_D(TAG, "Received data: %s", data_cstr);
+    widget_reset(app->widget_file_read);
+
     if (str_result != NULL)
     {
         widget_add_text_scroll_element(
-            app_instance->textbox,
+            app->widget_file_read,
             0,
             0,
             128,
@@ -238,7 +438,7 @@ static bool load_received_data()
     else
     {
         widget_add_text_scroll_element(
-            app_instance->textbox,
+            app->widget_file_read,
             0,
             0,
             128,