# Chunked Filesystem Transfer System This system enables chunked file uploads and downloads between the client (web browser) and ESP32 over WebSocket, overcoming the 1KB per stream limitation. ## Architecture ### Protocol Messages (Protobuf) The system uses Protocol Buffers for efficient binary messaging with the following operations: 1. **Delete**: Remove files or directories 2. **Mkdir**: Create directories 3. **List**: List files and directories 4. **Download**: Transfer files from ESP32 to client (chunked) 5. **Upload**: Transfer files from client to ESP32 (chunked) 6. **Cancel**: Cancel ongoing transfers ### Chunked Transfer Flow #### Download (ESP32 → Client) 1. Client sends `FSDownloadStartRequest` with file path 2. ESP32 responds with `FSDownloadStartResponse` containing: - Transfer ID - File size - Chunk size (1024 bytes max) - Total chunks 3. Client requests chunks sequentially using `FSDownloadChunkRequest` 4. ESP32 sends each chunk via `FSDownloadChunkResponse` 5. Transfer completes when last chunk is received #### Upload (Client → ESP32) 1. Client sends `FSUploadStartRequest` with destination path and file size 2. ESP32 responds with `FSUploadStartResponse` containing: - Transfer ID - Max chunk size (1024 bytes) 3. Client sends chunks sequentially using `FSUploadChunkRequest` 4. ESP32 responds with `FSUploadChunkResponse` after each chunk 5. Transfer completes when last chunk is written ## Implementation Details ### ESP32 Side **Files:** - `esp32/include/filesystem_ws.h` - Header file with handler class definition - `esp32/src/filesystem_ws.cpp` - Implementation of filesystem operations - `esp32/include/communication/proto_helpers.h` - Message traits for protobuf **Key Features:** - Transfer state management with automatic cleanup - Timeout handling (30 seconds of inactivity) - Recursive directory deletion - File integrity verification **Integration:** You need to integrate the filesystem handlers into your WebSocket message handling. In your main WebSocket handler, add: ```cpp #include // In your correlation request handler: void handleCorrelationRequest(const socket_message_CorrelationRequest& request, int clientId) { socket_message_CorrelationResponse response; // ... existing handlers ... if (request.which_request == socket_message_CorrelationRequest_fs_delete_request_tag) { response.which_response = socket_message_CorrelationResponse_fs_delete_response_tag; response.response.fs_delete_response = FileSystemWS::fsHandler.handleDelete(request.request.fs_delete_request); } else if (request.which_request == socket_message_CorrelationRequest_fs_mkdir_request_tag) { response.which_response = socket_message_CorrelationResponse_fs_mkdir_response_tag; response.response.fs_mkdir_response = FileSystemWS::fsHandler.handleMkdir(request.request.fs_mkdir_request); } else if (request.which_request == socket_message_CorrelationRequest_fs_list_request_tag) { response.which_response = socket_message_CorrelationResponse_fs_list_response_tag; response.response.fs_list_response = FileSystemWS::fsHandler.handleList(request.request.fs_list_request); } else if (request.which_request == socket_message_CorrelationRequest_fs_download_start_request_tag) { response.which_response = socket_message_CorrelationResponse_fs_download_start_response_tag; response.response.fs_download_start_response = FileSystemWS::fsHandler.handleDownloadStart(request.request.fs_download_start_request); } else if (request.which_request == socket_message_CorrelationRequest_fs_download_chunk_request_tag) { response.which_response = socket_message_CorrelationResponse_fs_download_chunk_response_tag; response.response.fs_download_chunk_response = FileSystemWS::fsHandler.handleDownloadChunk(request.request.fs_download_chunk_request); } else if (request.which_request == socket_message_CorrelationRequest_fs_upload_start_request_tag) { response.which_response = socket_message_CorrelationResponse_fs_upload_start_response_tag; response.response.fs_upload_start_response = FileSystemWS::fsHandler.handleUploadStart(request.request.fs_upload_start_request); } else if (request.which_request == socket_message_CorrelationRequest_fs_upload_chunk_request_tag) { response.which_response = socket_message_CorrelationResponse_fs_upload_chunk_response_tag; response.response.fs_upload_chunk_response = FileSystemWS::fsHandler.handleUploadChunk(request.request.fs_upload_chunk_request); } else if (request.which_request == socket_message_CorrelationRequest_fs_cancel_transfer_request_tag) { response.which_response = socket_message_CorrelationResponse_fs_cancel_transfer_response_tag; response.response.fs_cancel_transfer_response = FileSystemWS::fsHandler.handleCancelTransfer(request.request.fs_cancel_transfer_request); } // Send response back to client sendCorrelationResponse(response, clientId); } // Optionally, in your main loop or timer: void loop() { // Clean up expired transfers periodically FileSystemWS::fsHandler.cleanupExpiredTransfers(); } ``` ### Client Side (TypeScript/Svelte) **Files:** - `app/src/lib/filesystem/chunkedTransfer.ts` - Client library for file transfers - `app/src/lib/components/filesystem/FileManager.svelte` - Example UI component **Usage Example:** ```typescript import { fileSystemClient } from '$lib/filesystem/chunkedTransfer' // Upload a file const file = new File(['Hello World'], 'test.txt') const result = await fileSystemClient.uploadFileFromBrowser('/test.txt', file, (progress) => { console.log(`Upload: ${progress.percentage}%`) }) // Download a file const download = await fileSystemClient.downloadFileAndSave( '/test.txt', 'test.txt', (progress) => { console.log(`Download: ${progress.percentage}%`) } ) // List directory const listing = await fileSystemClient.listDirectory('/') console.log('Files:', listing.files) console.log('Directories:', listing.directories) // Create directory await fileSystemClient.createDirectory('/new_folder') // Delete file await fileSystemClient.deleteFile('/old_file.txt') ``` ## Configuration ### Maximum Chunk Size Currently set to 1024 bytes (`FS_MAX_CHUNK_SIZE`) to work within ESP32 WebSocket frame limitations. Adjust if your setup allows larger frames. ### Transfer Timeout Transfers inactive for 30 seconds (`FS_TRANSFER_TIMEOUT`) are automatically cleaned up. Increase for slower connections. ## Error Handling - Network errors: Transfers are automatically cancelled - Timeouts: Inactive transfers are cleaned up on ESP32 - File errors: Detailed error messages returned to client - Partial uploads: Cancelled uploads delete the partial file on ESP32 ## Performance Considerations - **Sequential Chunks**: Chunks are sent sequentially to ensure order and reliability - **Memory Usage**: ESP32 keeps one File handle open per active transfer - **Browser Memory**: Downloads buffer entire file in memory before saving - **Network**: ~1KB per message overhead due to protobuf encoding ## Security Notes - No authentication/authorization implemented - add as needed - Path traversal: Validate paths to prevent access outside allowed directories - File size limits: Consider adding max file size restrictions - Rate limiting: Consider limiting concurrent transfers per client ## Testing 1. Build and flash the ESP32 firmware 2. Run the web application 3. Navigate to the FileManager component 4. Test upload/download with files of various sizes ## Troubleshooting **Transfer fails midway:** - Check WebSocket connection stability - Verify ESP32 has sufficient filesystem space - Check for timeout issues **Upload creates corrupted files:** - Verify chunk order is preserved - Check for protobuf encoding/decoding errors **ESP32 runs out of memory:** - Reduce number of concurrent transfers - Close File handles properly after transfers - Run cleanup more frequently