esmp3/src/http_server.cpp

168 lines
5.8 KiB
C++

#include "http_server.h"
#include "main.h"
#include "spi_master.h"
#include <ESPmDNS.h>
HTTPServer::HTTPServer(Player* p, Controller* c) {
_player = p;
_controller = c;
_server = new AsyncWebServer(80);
ws = new AsyncWebSocket("/ws");
_server->addHandler(ws);
ws->onEvent([&](AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){this->_onEvent(server, client, type, arg, data, len);});
_server->on("/", HTTP_GET, [&](AsyncWebServerRequest* req) {
req->send(200, "text/html", (const char*)file_index_html_start);
});
_server->on("/upload", HTTP_POST, [](AsyncWebServerRequest* req) {
req->send(200);
}, [&](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
this->_handle_upload(request, filename, index, data, len, final);
});
_server->on("/_mapping.txt", HTTP_GET, [&](AsyncWebServerRequest* req) {
req->send(200, "text/plain", _controller->pm->create_mapping_txt());
});
_server->on("/player.json", HTTP_GET, [&](AsyncWebServerRequest* req) {
req->send(200, "application/json", _controller->player->json());
});
_server->on("/playlist_manager.json", HTTP_GET, [&](AsyncWebServerRequest* req) {
req->send(200, "application/json", _controller->pm->json());
});
_server->on("/controller.json", HTTP_GET, [&](AsyncWebServerRequest* req) {
req->send(200, "application/json", _controller->json());
});
_server->on("/position.json", HTTP_GET, [&](AsyncWebServerRequest* req) {
req->send(200, "application/json", _controller->player->position_json());
});
_server->on("/cmd", HTTP_POST, [&](AsyncWebServerRequest *req) {
if (req->hasParam("cmd", true)) {
_controller->queue_command(req->getParam("cmd", true)->value());
req->send(200);
} else {
req->send(400);
}
});
_server->begin();
MDNS.addService("http", "tcp", 80);
}
void HTTPServer::_handle_upload(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final) {
// https://www.gnu.org/software/tar/manual/html_node/Standard.html
// https://www.mkssoftware.com/docs/man4/tar.4.asp
if (index == 0) { // Starting upload
_chunk = new uint8_t[512];
_chunk_length = 0;
_upload_position = 0;
_file_size = 0;
_file_size_done = 0;
_need_header = true;
}
uint32_t upload_offset = 0;
while (upload_offset < len) {
// Load a chunk
if (_chunk_length < 512 && len > upload_offset) {
uint16_t needed = 512 - _chunk_length;
if (needed > len - upload_offset) needed = len - upload_offset;
memcpy(_chunk + _chunk_length, data + upload_offset, needed);
_chunk_length += needed;
upload_offset += needed;
_upload_position += needed;
if (_chunk_length == 512) {
// Process chunk
DEBUG(".");
if (_need_header) {
if (_chunk[257]=='u'&&_chunk[258]=='s'&&_chunk[259]=='t'&&_chunk[260]=='a'&&_chunk[261]=='r') {
DEBUG("It is a valid header, starting at 0x%X!\n", _upload_position-512);
char filename[200];
strncpy(filename, (char*)_chunk, 100);
DEBUG("filename: %s\n", filename);
_file_size = 0;
_file_size_done = 0;
for (int i=0; i<11; i++) {
//Serial.print(_header_buffer[124 + i]);
_file_size = (_file_size<<3) + (_chunk[124 + i] - '0');
}
DEBUG("filesize: %d\n", _file_size);
uint8_t type = _chunk[156] - '0';
if (type==0) {
String path = "/";
path += filename;
DEBUG("Opening file %s\n", path.c_str());
uint8_t state = SPIMaster::state;
SPIMaster::disable();
SPIMaster::select_sd();
// Better safe than sorry. ;-)
_upload_file.close();
_upload_file = SD.open(path, "w");
SPIMaster::set_state(state);
} else if (type==5) {
String dirname = "/";
dirname += filename;
dirname.remove(dirname.length()-1);
uint8_t state = SPIMaster::state;
SPIMaster::disable();
SPIMaster::select_sd();
bool res = SD.mkdir(dirname);
SPIMaster::set_state(state);
DEBUG("Creating folder '%s' returned %d.\n", dirname.c_str(), res);
} else {
ERROR("Unknown file type %d\n", type);
}
_need_header = (type==5 || _file_size==0); // No chunks needed for directories.
} else {
bool byte_found = false;
for (int i=0; i<512; i++) byte_found = byte_found || _chunk[i]>0;
if (!byte_found) {
DEBUG("Empty chunk while looking for header -> ignoring.\n");
} else {
ERROR("Invalid tar header: %c %c %c %c %c. Looking at header start offset 0x%X.\n", _chunk[257], _chunk[258], _chunk[259], _chunk[260], _chunk[261], _upload_position-512);
}
}
} else {
uint32_t bytes_to_write = _file_size - _file_size_done;
if (bytes_to_write > 512) bytes_to_write=512;
uint8_t state = SPIMaster::state;
SPIMaster::disable();
SPIMaster::select_sd();
_upload_file.write(_chunk, bytes_to_write);
_file_size_done += bytes_to_write;
if (_file_size_done >= _file_size) {
_upload_file.close();
_need_header = true;
}
SPIMaster::set_state(state);
}
_chunk_length = 0;
}
}
}
if (final == true) {
uint8_t state = SPIMaster::state;
SPIMaster::disable();
SPIMaster::select_sd();
_upload_file.close();
SPIMaster::set_state(state);
delete _chunk;
_controller->update_playlist_manager();
return;
}
}
void HTTPServer::_onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len) {
if (type==WS_EVT_CONNECT) {
_controller->inform_new_client(client);
} else if (type==WS_EVT_DATA) {
AwsFrameInfo* info = (AwsFrameInfo*) arg;
if (info->final && info->index==0 && info->len==len && info->opcode==WS_TEXT) {
data[len]='\0';
DEBUG("Received ws message: %s\n", (char*)data);
_controller->queue_command((char*)data);
}
}
}