212 lines
8.0 KiB
Markdown
212 lines
8.0 KiB
Markdown
# 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 <filesystem_ws.h>
|
|
|
|
// 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
|